🧪 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/chat_test.php |
2 | Log de auditoria do chat do assistente: a mensagem do professor registrada é cortada em 255 caracteres, nunca no meio de um caractere multibyte (log_chat() acessado por reflexão, sem chamar a IA) |
ai/generator_test.php |
19 | save_item() (via reflection, sem rede): limita um nome gerado por IA acima do tamanho; converte campos não-string antes de persistir; sanitiza uma descrição maliciosa; is_safe_url() rejeita um host que não resolve e um literal de IP não-canônico (guarda de SSRF) |
ai/hub_usage_reporting_test.php |
2 | Reporta toda tentativa de IA que falhou quando uma chave do hub é usada, não só a última; nunca reporta uso de uma chave própria do plugin (fora do hub) |
backup_restore_item_images_test.php |
1 | Imagens de item: o arquivo de imagem de um item é restaurado exatamente uma vez, com o novo id do item |
backup_restore_test.php |
3 | Definições de backup/restore cobrem todas as tabelas RPG; round-trip completo de curso (incl. duas atividades reais) 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; um segundo item deadline_extension com seu próprio cmid confirma que o lookup em lote de after_restore() não mistura o cmid de um item com o de outro |
block_dashboard_test.php |
3 | Tratamento do Painel: o bloco não é um formato aplicável no Painel, mas uma instância deixada lá por uma versão anterior continua podendo ser excluída (user_can_addto()); páginas de curso mantêm a regra padrão |
collection_tab_test.php |
17 | 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”); get_lp_activities() é memoizado entre múltiplos itens na mesma renderização (N+1 corrigido); has_power() é falso pra action_type não reconhecido e pra deadline_extension sem o plugin companheiro instalado; uma contagem de origem grande renderiza compacta; um item possuído só pelo motor novo de quantidade (sem linhas legadas de inventário) ainda conta e ganha o badge “novo”; um saldo dividido entre as duas gerações de armazenamento soma corretamente num único card; uma quantidade alta renderiza pelo utils::format_compact_number(); get_lp_activities() exclui uma atividade oculta mesmo com regra de penalidade por atraso habilitada, enquanto uma visível com a mesma regra continua aparecendo; um item de prazo que fixa um único cmid oculto resolve pra uma lista de atividades vazia em vez de vazar o nome dela |
db_access_test.php |
2 | O riskbitmask declarado de block/playerhud:manage inclui RISK_PERSONAL, já que o painel de gerenciamento expõe XP, inventário e atribuição de classe de outros usuários |
db_upgrade_test.php |
9 | Passos de upgrade com lógica real de migração de dados (diferente de DDL de schema puro): marca um item pré-existente literalmente chamado ‘PlayerCoin’ com action_type=playercoin, deixa intocados itens não relacionados/já marcados; retroalimenta xpawarded numa linha de inventário pré-existente de uma origem que paga XP de verdade (map/teacher/revoked) usando o XP atual do item, mas não para uma linha de origem drop nem uma linha coletada de um drop infinito (maxusage=0); retroalimenta o xpawarded de uma reivindicação de quest pré-existente a partir do reward_xp atual da quest; retroalimenta um código de drop faltante com um código único que não colide com um já existente na mesma instância; o passo de upgrade adiciona as tabelas block_playerhud_stack/block_playerhud_stack_log e as colunas de quantidade de drop/quest sem tocar em nenhuma linha existente; adiciona a coluna timerevoked em block_playerhud_stack_log sem tocar em nenhuma linha existente |
drop_guard_test.php |
11 | Limites de coleta, itens consumidos por troca, aplicação de cooldown; um limite de coleta é aplicado a partir de um saldo mantido só no log do motor novo de quantidade; um limite combinando linhas legadas de inventário e linhas do log novo bloqueia corretamente; um saldo combinado das duas fontes abaixo do limite ainda é permitido; um cooldown é aplicado a partir da coleta mais recente do motor novo, não só de uma linha legada de inventário |
edit_form_test.php |
5 | Validação do formulário de configuração do bloco: “XP por nível” abaixo de um é rejeitado, um valor positivo é aceito |
form/edit_item_form_test.php |
8 | Validação server-side do campo de imagem do item: valor emoji passa; URL HTTPS válida passa; payload de quebra de atributo (http seguido de aspas/tags) é rejeitado; valor http-like malformado que não é uma URL de verdade é rejeitado |
form/edit_scene_form_test.php |
3 | Rótulos de “próxima cena” no formulário de cena: cortados por caracteres (UTF-8 válido), entidades HTML decodificadas, reticências só quando houve corte |
game_test.php |
48 | 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, com o emoji do avatar escapado (strip_tags) em build_trade_suggestions; change_xp emite o evento xp_changed ao conceder, ao deduzir (piso em zero) e fica em silêncio num no-op de verdade; get_leaderboard sinaliza corretamente o grupo do próprio usuário no ranking por grupo e restringe o filtro de grupo do professor aos membros reais; process_collection() concede várias unidades de uma vez através do campo value do drop; maxusage e value se multiplicam de forma independente (um drop com 3 usos e 2 por uso rende 6 no total ao longo de 3 coletas, bloqueado na 4ª tentativa independente do value); a resposta da coleta reporta a quantidade concedida e o texto de progresso; get_leaderboard() pode pular o nome e a data de exibição, que add_display_fields() acrescenta depois só às entradas mostradas |
gamemaster_test.php |
6 | Conceder, revogar e excluir item e quest pelos controllers reais: o XP é aplicado, a data de desempate do ranking só avança em ganho, XP mínimo em zero |
instance_delete_test.php |
2 | Excluir uma instância do bloco limpa todas as tabelas próprias do plugin (instance_cleanup), incl. as novas tabelas block_playerhud_stack/block_playerhud_stack_log; uma guarda lê todas as tabelas do install.xml e exige que o fixture as cubra e que a limpeza as esvazie, para que uma tabela nova não seja esquecida |
item_delete_cascade_test.php |
25 | 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); a exclusão única e em lote de um item também remove suas linhas de block_playerhud_stack/block_playerhud_stack_log e reverte o XP que elas registraram; excluir um item depois que uma de suas unidades do stack já foi revogada (isolada ou por uma revogação legada em lote) reverte só o XP ainda pendente, sem reverter em dobro a parte já recuperada; find_xp_impact ignora um detentor cujo saldo inteiro do stack já foi revogado |
karma_test.php |
11 | Leitura/escrita de karma, deltas positivos/negativos, clamping nos limites ±999, acumulação sucessiva |
lib_test.php |
27 | block_playerhud_myprofile_navigation: todo ramo de no-op (sem curso, curso do site, sem instância do bloco, sem registro de jogador, gamificação desabilitada) e um jogador ativo com item coletado recebe a seção de perfil, incl. um viewer estudante comum (não admin); a seção só é montada quando o viewer tem block/playerhud:view e moodle/block:view no contexto do bloco (capability negada de qualquer um dos dois é no-op); um estudante oculto do leaderboard (ranking_visibility = 0) mantém o perfil visível, pois só pausar a gamificação o remove, e o próprio dono e um professor com block/playerhud:manage também o veem; block_playerhud_get_drop_details_by_code: correspondência, código desconhecido, rejeição de instância estrangeira, exclusão de item desabilitado; block_playerhud_is_visible_for_class: público (vazio/’0’), classe correspondente/não correspondente, ‘0’ dentro de uma lista; block_playerhud_pluginfile: contexto não-bloco, área de arquivo desconhecida, nenhum arquivo armazenado encontrado, e a mesma checagem de block/playerhud:view/moodle/block:view da seção de perfil (qualquer um dos dois negado rejeita o download, um estudante comum com ambos passa) antes de servir a arte de item/classe |
privacy_provider_test.php |
26 | 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), incl. xp_gained de missão e created/modified do progresso de RPG, que antes ficavam de fora; exclusão por usuário, multiusuário e de contexto inteiro com garantia de isolamento, incl. a limpeza de wizard_objects/wizard_shortcodes antes de wizard_runs (evitando linhas órfãs com runid inexistente); exportação/exclusão de toda chave de API e preferência de avatar; declaração de metadados completa por coluna real da tabela (não por chave individual) para playerhud_user, inventory, ai_logs, wizard_runs, rpg_progress, quest_log e trade_log; guardas de contexto não-bloco como no-ops; a declaração de metadados também é checada contra toda coluna real das novas tabelas block_playerhud_stack e block_playerhud_stack_log; get_contexts_for_userid() ainda encontra um usuário cujo único dado mora numa única tabela, e sua contagem de queries de descoberta se mantém estável não importa quantas tabelas do plugin realmente têm dado desse usuário (guarda N+1); uma requisição de contexto alcança a checagem da tabela de concessão dentro de add_contextlist_for_user() mesmo pra um usuário sem nenhuma linha em playerhud_user |
quest_test.php |
53 | 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; preload_totals() bate com os resultados não-agrupados do próprio check_status() por quest em todo tipo apoiado em query, incl. duas quests TYPE_SPECIFIC_ITEM/TYPE_SPECIFIC_TRADE com ids diferentes, e sua contagem de leituras se mantém estável conforme a lista de quests cresce em vez de escalar; has_claimable_quests() mantém suas próprias contagens por tipo isoladas entre itens/trocas e nunca paga por um agregado de um tipo que uma quest anterior na lista já curto-circuitou antes de alcançar; 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; TYPE_SPECIFIC_ITEM/TYPE_UNIQUE_ITEMS/TYPE_TOTAL_ITEMS são satisfeitos por um saldo mantido só no motor novo de quantidade, não só em linhas legadas de inventário; reivindicar uma recompensa concede o reward_itemqty configurado da quest, não sempre exatamente uma unidade; a mensagem de recompensa mantém o espaçamento do conector intacto |
rpg_classes_test.php |
3 | Consulta de classe (get_player_class) e limites de tier de retrato |
story_manager_test.php |
34 | Carregamento de cena, persistência de progresso, navegação de escolhas, delta de karma, conclusão de capítulo, casos de erro; a consequência set_class_id de uma escolha atribui a classe RPG do jogador — o único caminho de atribuição — e dispara character_selected; make_choice() adquire e libera o lock por usuário sem travar numa chamada normal; inventário consumed/revoked é rejeitado como pagamento de uma escolha com custo, uma cópia genuína é aceita e consumida (e soft-consumida — vira source=consumed, não é excluída, então a linha sobrevive); load_scene(), make_choice() e load_recap() revalidam unlock_date/required_level do capítulo no servidor (antes só a UI bloqueava o clique), com load_recap() isento dessa checagem para um capítulo já concluído — reler algo legitimamente terminado nunca re-trava, mesmo que o professor aperte as regras depois; gastar um item numa escolha de história nunca devolve uma coleta do drop finito/com cooldown de onde o item veio, já que o drop_guard continua contando a linha (agora consumida, não excluída) contra o maxusage do drop; um custo de escolha é aceito quando o pagamento vem só do saldo do motor novo de quantidade; um custo de escolha é aceito quando o pagamento é dividido entre as duas gerações de armazenamento |
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 |
template_strings_test.php |
3 | Os templates tiram seus textos visíveis e anunciados de strings de idioma: rótulos das medalhas do ranking, rótulo do emoji do gerador de shortcode e a unidade de respawn do modal de IA (verificado com um en_local personalizado) |
trade_test.php |
12 | 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; uma troca de quantidade alta concede/consome pelo motor de quantidade sem inserir uma linha de inventário por unidade; o custo de uma troca é pago corretamente quando dividido entre as duas gerações de armazenamento |
uninstall_test.php |
2 | O hook de pré-desinstalação (xmldb_block_playerhud_uninstall()) exclui só as linhas de user_preferences com prefixo block_playerhud_, deixando intocada uma preferência de outro plugin (mesmo uma que compartilha o trecho playerhud, ex.: filter_playerhud_pref) e uma preferência não relacionada; rodar o hook sem nada pra excluir é um no-op inofensivo |
utils_test.php |
23 | 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; generate_drop_code() retorna um código de 6 caracteres alfanuméricos maiúsculos, e gerar/persistir vários códigos seguidos para a mesma instância nunca produz um repetido; sanitize_rich_description() remove um esqueleto de modal colado, preserva formatação básica, remove uma tag <script> nua, e neutraliza uma URI javascript:; format_compact_number() deixa um valor abaixo de 1000 inalterado, usa sufixo k pra milhares, sufixo M pra milhões, e preserva o sinal negativo; format_drop_progress() renderiza tanto a contagem quanto o badge pra um limite de coleta finito e um ilimitado; formatação da quantidade por coleta do drop; get_block_config() devolve uma configuração vazia para configdata NULL, vazio ou inválido, e a configuração real nos demais casos |
| Subtotal | 364 |
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 |
13 | 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(); o teto de XP alcançável considera o value de um drop, não só sua contagem de itens |
audit_log_test.php |
16 | 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; uma concessão/consumo/revogação do motor novo aparece cada uma como seu próprio tipo de evento, com a revogação reportando um valor negativo; o details de um consumo ganha o prefixo consumed_ pra resolver pra sua própria string report_src_consumed_* em vez de colidir com a tag do lado da concessão de mesmo nome, enquanto um consumo sem tag de origem mantém o details genérico consumed sem prefixo; uma concessão legada reporta quantidade um e um consumo legado reporta quantidade menos um; uma concessão/consumo/revogação do motor novo reportam sua quantidade real (positiva/negativa) em vez de sempre um; eventos de troca e quest sempre reportam quantidade zero |
diagnostics/integrity_test.php |
14 | Integridade (diagnóstico): os mapas de tabelas de órfãs e de registros soltos cobrem todas as tabelas do install.xml; uma instância cuja linha em block_instances sumiu (ou de id 0) é apontada, com jogadores, XP e itens, e com o curso recuperado do registro real de xp_changed no log, marcado como existente ou apagado; órfãs cujo curso ainda existe vêm primeiro e a lista tem limite; registros soltos são contados por tabela; a limpeza apaga só a órfã selecionada e todos os registros soltos (inclusive uma escolha que fica solta quando a cena dela sai), deixa a instância viva intacta, ignora um id vivo enviado pelo formulário, não grava cópia quando não há nada a apagar, e salva uma cópia JSON que pode ser listada, encontrada só pelo nome exato e apagada |
diagnostics/report_test.php |
3 | Relatório de diagnóstico: todas as seções que a página lê estão presentes; um relatório recente é reaproveitado até ser atualizado ou invalidado; um relatório com mais de dez minutos é recalculado |
diagnostics/usage_test.php |
2 | Números de diagnóstico: a adoção por curso se divide em três grupos que somam o total, conta os recursos ativados e separa um bloco no Painel; o engajamento conta jogadores, desistências e XP nas instâncias vivas, e a atividade só dentro da janela de 90 dias, deixando de fora os registros de instâncias removidas |
drop_distribution_test.php |
13 | 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), exclui labels quando solicitado, 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 |
27 | 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() atualiza o saldo do motor novo de quantidade e concede XP para o próprio item habilitado de quem chama, e registra o dropid que disparou a concessão na linha do log; consume() gasta primeiro do saldo novo, cai para linhas legadas de inventário quando o saldo novo está vazio, gasta corretamente quando o saldo está dividido entre as duas gerações, e retorna false quando insuficiente nas duas; get_available_quantity() soma as duas gerações de armazenamento; get_available_quantities_bulk() bate com o que consultar cada item individualmente retornaria, e sua contagem de leituras se mantém estável conforme a lista de itens cresce em vez de escalar; get_name()/get_xp() resolvem para a própria instância do item e retornam vazio/zero para uma estrangeira |
latepenalty_bridge_test.php |
2 | Ponte com o local_latepenalty: o prazo de uma atividade é informado como prazo do estudante; sem prazo devolve null, não um timestamp zero |
wizard_test.php |
22 | 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; desfazer remove vários shortcodes registrados de atividades diferentes reconstruindo o cache do curso uma única vez, não uma vez por shortcode; desfazer exclui trocas e capítulos registrados (com seus trade_reqs e story_nodes) pelos mesmos caminhos em lote bulk_delete_trades()/bulk_delete_chapters() que os controladores de trocas/capítulos já têm, exercitado de ponta a ponta via wizard::rollback() em vez de chamar os métodos do controlador direto; 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; require_course_matches_instance() aceita o curso real da instância do bloco e rejeita um courseid de qualquer outro curso |
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 | 127 |
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 |
4 | Sem chave de API → moodle_exception, assegurando o errorcode específico accessdenied em vez de qualquer moodle_exception onde relevante (uma chave de API não configurada também lançaria uma e mascararia uma checagem de vínculo ausente); campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado antes de qualquer chamada de IA ser tentada |
collect_item_test.php |
5 | Item coletado + registro de inventário criado; drop inválido → success=false; limite atingido → success=false; guarda de capability (view); um courseid incompatível é rejeitado |
create_avatar_pack_test.php |
7 | 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; um courseid incompatível é rejeitado |
create_class_pack_test.php |
8 | 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; um courseid incompatível é rejeitado |
create_playercoin_test.php |
4 | Novo item criado; segunda chamada retorna existente (idempotência); guarda de capability; um courseid incompatível é rejeitado |
execute_chat_action_test.php |
6 | 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; um courseid incompatível é rejeitado |
generate_ai_content_test.php |
4 | Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado |
generate_class_oracle_test.php |
4 | Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado |
generate_story_test.php |
4 | Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado |
insert_drop_shortcode_test.php |
12 | 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; execute_batch() insere shortcodes para vários drops numa só chamada (usada pela distribuição do assistente), e uma falha isolada num drop não bloqueia o processamento dos demais; dois drops caindo na mesma atividade e campo no mesmo lote sobrevivem ambos (o cache de valores de campo pré-carregado é atualizado in-place a cada escrita, não lido uma vez só pro lote inteiro); a contagem de leituras do lote se mantém estável conforme cresce em vez de escalar com o número de itens |
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 |
4 | 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 |
8 | Shortcode existente removido; shortcode separado por <br> removido; shortcode com atributos mode=/text= removido; ausência de shortcode é noop sem erro; guarda de capability; execute_batch() remove o shortcode de vários drops numa só chamada reconstruindo o cache do curso uma única vez; a variante em lote tem sua própria guarda de capability |
setup_playercoin_drop_test.php |
7 | 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 |
13 | Guarda de capability (view, assegurada via uma capability dedicada interact); item não possuído → exceção; poder de prazo: sem atividade, sem regra, cria override e consome item, atualiza override existente, rejeita um targetcmid de outro curso mesmo quando já existe um override pra esse cmid+userid, rejeita um cmid estrangeiro antes mesmo de consultar a tabela de regra do plugin companheiro, e rejeita um targetcmid que o usuário atual não pode ver (atividade oculta) com a mesma mensagem exata de “sem regra configurada”, rodado como estudante em vez de admin pra realmente exercitar a checagem de visibilidade; duas concessões sequenciais legítimas (dois itens em posse) consomem cada uma sua própria unidade e estendem exatamente um bloco de dias por chamada, sem dobrar a extensão de nenhuma das duas; poder de avatar: equipar e desequipar funcionam mesmo com $OUTPUT resetado pro placeholder bootstrap_renderer ainda não resolvido, reproduzindo a mesma condição de uma requisição AJAX real, e equipar continua funcionando pra um avatar concedido só pelo motor novo de quantidade |
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 |
13 | 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, espalha entre todas as atividades elegíveis pulando labels; um id de drop de outra instância é silenciosamente excluído em vez de distribuído, sem vazar seu nome nem inflar a contagem de cota; uma lista de drop ids vazia retorna cedo em vez de chegar no get_in_or_equal() sem nada pra casar |
wizard_list_runs_test.php |
4 | Resumo de uma rodada ativa; rodada de RPG resumida; rodadas desfeitas excluídas; guarda de capability |
wizard_rollback_test.php |
4 | Exclui os objetos gerados pela rodada, contagem reportada bate com o que foi registrado; rejeita instância incompatível; guarda de capability |
wizard_run_step_test.php |
57 | 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; um courseid que não é o dono de fato da instância do bloco é rejeitado |
wizard_start_test.php |
9 | 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; um courseid que não é o dono de fato da instância do bloco é rejeitado |
| Subtotal | 186 |
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 |
17 | 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; bulk_delete_chapters() remove vários capítulos junto com suas cenas e escolhas numa só passada, filtra um capítulo de outra instância em vez de excluí-lo ou lançar exceção, e é no-op para lista de ids vazia; mover um capítulo não escala o número de escritas com o total de capítulos |
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 |
drops_test.php |
19 | 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; get_sort_data alterna ícone/direção da coluna de ordenação ativa; view_manage_page/handle_edit_form renderizam de ponta a ponta pelo $OUTPUT/$PAGE globais reais para um professor com a capability de gerenciamento; o template da tabela de gerenciamento escapa o conteúdo de mídia do drop; view_manage_page ordena corretamente por nome e cai numa coluna segura diante de um parâmetro de ordenação desconhecido em vez de interpolá-lo no SQL; a tela do assistente de distribuir drops escapa o atributo de dados dos cmids inseridos, e o template da sidebar escapa a URL de gerenciamento |
export_test.php |
10 | 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; o email de um estudante é omitido da exportação quando oculto pela política de identidade do site |
items_test.php |
26 | Ciclo de vida do item: toggle de ativação e no-op estrangeiro; conceder adiciona inventário + XP, 0 XP, rejeição estrangeira, e rejeita um usuário que não está de fato matriculado no curso da instância; revogar desconta XP, preserva drop infinito, no-op estrangeiro, é idempotente numa segunda revogação (XP descontado só uma vez), e é no-op numa cópia já consumida; 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; conceder com um $qty explícito concede várias unidades pelo motor de quantidade numa só chamada; revogar uma entrada de block_playerhud_stack_log desconta o saldo e o XP registrado, limita a remoção ao saldo atual, é no-op numa linha de consume, é idempotente numa segunda revogação da mesma entrada (mantendo intactas as unidades de outras concessões do mesmo usuário), deixa a concessão de origem sem sinalizar quando seu saldo já foi todo gasto, e é no-op numa entrada de instância estrangeira |
manage_entry_points_test.php |
34 | A metade HTTP dos controladores, exercitada pelo ciclo real de requisição (superglobais preenchidas como um navegador faria, redirect() capturado como redirecterrordetected sob CLI): as ações de excluir e excluir em lote de drops realmente removem as linhas, um id de drop de outra instância nunca é excluído, uma sesskey errada não exclui nada, a listagem renderiza só os drops da própria instância e cai num campo de ordenação seguro diante de um parâmetro sort forjado; excluir uma cena cascateia para suas escolhas, um nó de outro capítulo fica intacto, um capítulo de outra instância é rejeitado; coletar concede o item e seu XP, não paga XP em drop infinito, rejeita drop de outra instância, item desabilitado e sesskey inválida sem gravar nada; os editores de classe e troca rejeitam um registro de outra instância, e todas essas telas ficam fechadas para quem não tem block/playerhud:manage (ou :view no coletar); uma requisição genuinamente submetida (com o marcador _qf__ do moodleform, como um POST real carrega) para uma troca de outra instância ainda é rejeitada antes de seus requisitos/recompensas serem lidos, fechando um oráculo que vazaria as contagens de linha de uma troca alheia no dimensionamento do formulário re-renderizado; um GET simples com repeats_req/repeats_give acima do normal é limitado em vez de alocar um número ilimitado de elementos de formulário, e scenes::clamp_repeats() limita um valor de repeats fornecido pelo cliente ao mesmo teto fixo; a página de drops, o coletar, e os editores de capítulos/classe/troca/cenas rejeitam cada um um courseid que não é o dono de fato da instância do bloco; os editores de troca e drop escapam um nome de item malicioso embutido no select de itens e no cabeçalho da página |
quests_test.php |
14 | 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; a prévia de impacto de XP de uma exclusão em lote exclui uma quest de outra instância assim que a lista de ids é escopada à instância |
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 |
10 | 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; bulk_delete_trades() remove várias trocas junto com seus requisitos, recompensas e log numa só passada, filtra uma troca de outra instância em vez de excluí-la ou lançar exceção, e é no-op para lista de ids vazia |
| Subtotal | 151 |
Testes de Saída / Renderer (tests/output/)
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
admin/diagnostics_test.php |
6 | Dados da página de diagnóstico: cada número vira um cartão, os que usam janela dizem isso e as solicitações à IA listam os provedores; o cartão do Painel só aparece quando há blocos lá; órfãs cujo curso ainda existe começam desmarcadas; o template real renderiza o formulário de limpeza, os registros soltos em palavras e a lista de cópias, ou a mensagem de ‘nada encontrado’; uma lista parcial de órfãs diz quantas estão sendo mostradas |
chapters_escaping_test.php |
8 | Nomes escapados exatamente uma vez: títulos de capítulo nas listas do professor e do estudante, título e selos do editor de cenas, e nomes de classe e item que uma escolha da história envia ao script do player como texto puro |
manage/item_delete_confirm_test.php |
12 | 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/item_disable_confirm_test.php |
3 | Contexto da confirmação de desativar item: lista as quests afetadas por papel e envia o toggle confirmado; um papel sem quests deixa seu bloco de fora; nomes são filtrados para texto puro |
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 |
manage/tab_config_test.php |
4 | Aba Config (resumo Economy Health): uma instância sem itens ou quests ainda exporta um resumo vazio bem formado, sem quebrar no economy_health(); um item com XP contribui pro detalhamento e pro total alcançável |
manage/tab_items_escaping_test.php |
7 | Nomes escapados exatamente uma vez: a lista de itens (incluindo a confirmação de exclusão), a lista de todas as drops e a tela de distribuição, com formatstringstriptags ligado e desligado |
manage/tab_items_test.php |
3 | Consultas de penalidade por atraso da aba de itens: só as atividades do próprio curso com regra ativa são oferecidas, e a consulta de regras é restrita aos ids das atividades do curso em vez de ler o site inteiro |
manage/tab_quests_test.php |
4 | Processamento do formulário de quest: um esqueleto de modal colado na descrição é sanitizado antes de salvar; formatação básica na descrição é preservada; o select de item de recompensa escapa um nome de item malicioso |
manage/tab_reports_test.php |
22 | Aba Relatórios: uma instância sem jogadores/itens/quests ainda exporta um resumo bem formado com o drill-down de auditoria inativo; display() renderiza HTML real de ponta a ponta pelo $OUTPUT global; mais de 30 logs de IA só exportam a primeira página (30) por padrão, mais recentes primeiro, e ai_showall=1 retorna todos; o fallback de inventory.source sem lang string report_src_* correspondente é repassado cru ao template como detail_text no drill-down de auditoria, e o tab_reports.mustache é comprovado que escapa via duplo-mustache na renderização; o tab_reports.mustache monta os badges de xp_gained/qty ele mesmo a partir de inteiros simples mais flags de positivo/negativo (os três estados: positivo, negativo, e o fallback neutro “-“), fechando a mesma brecha de escape por triple-mustache que a correção do detail_text já tinha fechado; o seletor de estudante da exportação omite o email de um estudante quando oculto pela política de identidade do site; o KPI “item mais coletado” e o total de itens de cada estudante somam as duas gerações de armazenamento; a tabela de estudantes é paginada em 50 com o contador seguindo o deslocamento, a ordenação vale entre páginas, uma página além do fim é ajustada à última, a busca por nome casa nas duas ordens do nome e escapa curingas de LIKE, a URL da paginação mantém busca e ordenação, a lista de seleção de estudante some acima de 200 jogadores (render real do template), e o total de itens fica certo numa página posterior tanto ordenando por XP (ids da página primeiro) quanto por itens |
player_screens_escaping_test.php |
18 | Nomes escapados exatamente uma vez: coleção e perfil do estudante, aba de classes, página de drops e grupos do ranking; nomes que sugestões e o assistente gravam (sugestões de quest e de troca, uma drop renomeada com o nome da atividade) ficam como texto puro e os formulários de sugestão os escapam ao imprimir |
profile_content_test.php |
1 | export_for_template() remove tags do campo de conteúdo da imagem do item |
quests_escaping_test.php |
5 | Nomes escapados exatamente uma vez: nomes de quest, recompensa, item exigido, troca e capítulo na lista de quests do professor e na aba de quests do estudante |
reports_escaping_test.php |
6 | Nomes escapados exatamente uma vez: a visão geral dos relatórios, o log de auditoria de um estudante (itens, custos de troca, quests) e o histórico do próprio estudante |
trades_escaping_test.php |
4 | Nomes escapados exatamente uma vez: nomes de troca, requisito e recompensa, títulos de popover e a confirmação de exclusão na aba de trocas do professor e na loja do estudante |
view/header_test.php |
2 | Cabeçalho do HUD: um jogador sem avatar equipado cai pra foto de perfil padrão, carregando nome/XP/nível sem badge de grupo (sem mod_playergroup); um item de avatar equipado substitui a foto de perfil padrão |
view/tab_chapters_test.php |
6 | Aba Capítulos: sem capítulos renderiza o estado vazio em vez de quebrar; um capítulo desbloqueado e não concluído é listado como disponível; um capítulo registrado na lista completed_chapters do jogador renderiza como concluído; um capítulo com data de desbloqueio futura renderiza como bloqueado; um capítulo com required_level acima do nível do jogador também renderiza como bloqueado, coerente com a checagem que o servidor agora aplica |
view/tab_history_test.php |
4 | Aba Log: um jogador sem eventos registrados ainda exporta um estado vazio bem formado, com os 5 cabeçalhos de coluna ordenável presentes; o fallback de inventory.source sem lang string report_src_* correspondente é repassado cru ao template como detail_text, e o tab_history.mustache é comprovado que escapa via duplo-mustache na renderização; o tab_history.mustache monta os badges de xp_gained/qty ele mesmo a partir de inteiros simples mais flags de positivo/negativo (os três estados: positivo, negativo, e o fallback neutro “-“), fechando a mesma brecha de escape por triple-mustache que a correção do detail_text já tinha fechado |
view/tab_quests_test.php |
6 | Aba Quests: sem quests renderiza a notificação de vazio em vez de quebrar; uma quest concluída e não reivindicada é listada com ação de reivindicar; uma quest já reivindicada mostra a data de reivindicação em vez da ação; get_type_label() mapeia toda constante de tipo de quest pra um rótulo e cai no fallback pra um tipo não reconhecido |
view/tab_ranking_test.php |
13 | Aba Ranking: desabilitado na config do bloco encerra antes de tocar em qualquer dado de jogador; um estudante visível vê o conteúdo do ranking; um estudante oculto vê seu próprio toggle de privacidade mas não o ranking; um professor sempre vê o conteúdo com os controles de filtro exclusivos de professor ativos; paginate() fatia 50 por página, ajusta páginas fora do intervalo e fixa a linha do próprio estudante (com a posição real) só quando ela está fora da página; de ponta a ponta, um estudante em 87º de 120 vê as posições 1–50 mais a própria linha fixada com o nome, a segunda página do professor não tem linha fixada e o link de ocultar/mostrar mantém página e filtro; o template real renderiza a linha fixada e a barra de páginas |
view/tab_rules_test.php |
2 | Aba Regras/ajuda: uma config sem help_content cai no template padrão do sistema, carregando as flags de recurso habilitado dos cards de ajuda padrão; help_content customizado com use_default_help desabilitado renderiza o conteúdo próprio do professor em vez do padrão |
view/tab_shop_test.php |
4 | Aba Loja: sem trocas renderiza o estado vazio em vez de quebrar; um estudante com o item exigido em quantidade suficiente pode pagar a troca; um estudante sem o item exigido não pode pagar; uma troca de uso único já concluída é marcada como tal |
| Subtotal | 147 |
| Total geral | 975 |
vendor/bin/phpunit --testsuite block_playerhud
Cobertura de linhas por classe (PHPUnit + Xdebug):
| Classe | Cobertura de linhas |
|---|---|
ai\chat |
10% |
ai\generator |
65% |
controller\aikeys |
100% |
controller\chapters |
63% |
controller\classes |
67% |
controller\collect |
100% |
controller\drops |
91% |
controller\export |
92% |
controller\items |
95% |
controller\quests |
99% |
controller\scenes |
75% |
controller\suggestions |
100% |
controller\trades |
79% |
drop_guard |
100% |
event\character_selected |
43% |
event\item_collected |
43% |
event\quest_collected |
43% |
event\trade_completed |
43% |
event\xp_changed |
43% |
external\chat_message |
79% |
external\collect_item |
100% |
external\create_avatar_pack |
85% |
external\create_class_pack |
80% |
external\create_playercoin |
91% |
external\execute_chat_action |
54% |
external\generate_ai_content |
78% |
external\generate_class_oracle |
68% |
external\generate_story |
72% |
external\insert_drop_shortcode |
94% |
external\load_recap |
100% |
external\load_scene |
80% |
external\make_choice |
79% |
external\remove_drop_shortcode |
92% |
external\setup_playercoin_drop |
91% |
external\use_item |
82% |
external\wizard_apply_suggested_levels |
83% |
external\wizard_generate |
85% |
external\wizard_list_runs |
100% |
external\wizard_rollback |
100% |
external\wizard_run_step |
86% |
external\wizard_start |
100% |
form\edit_item_form |
75% |
form\edit_scene_form |
98% |
form\suggest_quests_form |
94% |
form\suggest_trades_form |
95% |
game |
95% |
instance_cleanup |
100% |
local\analytics |
92% |
local\audit_log |
80% |
local\diagnostics\integrity |
94% |
local\diagnostics\report |
100% |
local\diagnostics\usage |
100% |
local\drop_distribution |
74% |
local\external_items |
95% |
local\latepenalty_bridge |
50% |
local\rpg_archetypes |
92% |
local\wizard |
97% |
local\xp_budget |
98% |
output\admin\diagnostics |
100% |
output\manage\item_delete_confirm |
100% |
output\manage\item_disable_confirm |
100% |
output\manage\quest_delete_confirm |
100% |
output\manage\tab_chapters |
99% |
output\manage\tab_classes |
87% |
output\manage\tab_config |
82% |
output\manage\tab_items |
75% |
output\manage\tab_quests |
85% |
output\manage\tab_reports |
97% |
output\manage\tab_trades |
90% |
output\profile_content |
86% |
output\view\header |
95% |
output\view\tab_chapters |
100% |
output\view\tab_collection |
88% |
output\view\tab_history |
90% |
output\view\tab_quests |
91% |
output\view\tab_ranking |
83% |
output\view\tab_rules |
78% |
output\view\tab_shop |
92% |
privacy\provider |
97% |
quest |
96% |
story_manager |
79% |
trade_manager |
89% |
utils |
63% |
| Total | 81% |
83 das 91 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.
Os percentuais mais baixos da tabela refletem limites estruturais, não lacunas de teste:
ai\generator(65%) e os ramos de IA dechat_message/execute_chat_actionchamam provedores externos reais via curl, sem camada de mock HTTP.- A metade AJAX de
collect::execute()termina emdie(), submissões reais demoodleformdependem do navegador, e o comportamento movido por JavaScript não existe no lado do servidor — tudo isso é coberto pela suíte Behat abaixo. external\use_item(82%) tem um ramo de gate atômico contra corrida (consumir o item antes de escrever a extensão de prazo) que só falha sob concorrência real de duas requisições — uma limitação estrutural do PHPUnit de processo único documentada no próprio teste, verificada ao vivo via requisições HTTP genuinamente concorrentes em vez de coberta aqui.
Arquivos de Função Solta
O db/upgrade.php e o lib.php só definem funções globais, não classes, então o detalhamento
por classe do moodle-coverage acima não tem a quem atribuí-los. Os dois são instrumentados e
entram no percentual do Total; medidos isoladamente:
| Arquivo | Casos | Cobertura de linhas |
|---|---|---|
lib.php |
27 | 95% |
db/upgrade.php |
9 | 78% |
lib.php(tests/lib_test.php): as quatro funções são testadas diretamente —block_playerhud_myprofile_navigation()em todo ramo (sem curso, curso do site, sem instância do bloco, sem registro de jogador, gamificação desabilitada, jogador ativo com item recebe a seção de perfil, capabilityblock/playerhud:view/moodle/block:viewnegada, estudante oculto do ranking mas com perfil visível),block_playerhud_get_drop_details_by_code()(correspondência, código desconhecido, instância estrangeira, item desabilitado) eblock_playerhud_is_visible_for_class()(público, classe correspondente/não correspondente, ‘0’ dentro de uma lista).block_playerhud_pluginfile()é coberta até o ponto em que um arquivo correspondente é encontrado de verdade — o único ramo que fica de fora chamasend_stored_file(), que encerra o script, a mesma limitação estrutural já registrada pra metade AJAX docollect::execute()abaixo.db/upgrade.php(db_upgrade_test.php): os 9 testes que carregam lógica real de migração de dados chamamxmldb_block_playerhud_upgrade()diretamente, o que já basta pro Xdebug medir como qualquer outra chamada de função, mesmo o ambiente de teste do PHPUnit sempre instalando um schema novo a partir doinstall.xmle nunca executando o caminho automático de upgrade.
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 |
6 | HUD ativo na primeira visita, desativar/reativar gamificação, dispensar confirmação; abrir a aba Log não dá erro; equipar um item de avatar pelo round-trip AJAX real não dá erro |
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 |
block_playerhud_manage_crud.feature |
7 | As telas de gestão que os testes em PHP só alcançam isoladamente: as abas Trocas, Personagens e História renderizam numa requisição real; a biblioteca de itens leva até a tela de drops; um drop é criado pelo moodleform real, aparece na listagem e mostra a notificação de sucesso (travando a regressão do tipo de notificação do redirect()); um personagem é criado pelo formulário real (incluindo os campos de gerenciador de arquivos); o checkbox mestre de seleção em lote marca e desmarca todas as linhas (JavaScript) |
block_playerhud_manage_js.feature |
9 | O JavaScript das telas de personagens, configurações e missões: apagar um personagem depois de confirmar no modal, ou cancelar e mantê-lo; revelar e esconder uma chave de API; apagar uma missão depois de confirmar ou mantê-la ao recusar; o botão de exclusão em lote conta as missões selecionadas, volta a ficar desativado quando a seleção é limpa e apaga a seleção depois de confirmar |
block_playerhud_story_js.feature |
6 | O JavaScript da história: o estudante lê um capítulo até a cena final e abre o resumo da história; o professor faz a prévia de um capítulo até o fim sem salvar progresso, inclusive um que termina numa escolha “Fim do capítulo” e exclui um capítulo e uma cena pelos modais de confirmação |
block_playerhud_distribute_oracle_js.feature |
5 | Distribuição de drops: selecionar uma drop habilita o botão de inserir com a contagem, a inserção do shortcode numa atividade continua lá depois de recarregar, e desfazer com confirmação a devolve a pendente; o oráculo de personagens pede um tema antes de chamar a IA e, sem IA disponível, informa a falha e libera o botão |
block_playerhud_items_drops_js.feature |
11 | Itens e drops: o modal de prévia do item, exclusão de itens em lote e individual com confirmação, as opções de drop do gerador de itens e seus erros de tema e de falta de IA dentro do modal, os botões de PlayerCoin e de pacote de avatares, o código do gerador de shortcode em cada modo de exibição, exclusão de drops em lote e individual, e a contagem regressiva que o estudante vê numa drop em recarga |
block_playerhud_trades_reports_js.feature |
5 | Trocas e relatórios: excluir uma troca com confirmação, desmarcar e marcar de novo todas as sugestões de troca, a prévia do item escolhido como requisito no formulário de troca, abrir o log de um estudante pelo seletor e filtrá-lo pela busca ao vivo, e retirar um item de um estudante com confirmação |
block_playerhud_pagination.feature |
5 | Com 61 jogadores ranqueados: um estudante abaixo da primeira página vê a própria linha fixada sob ela; a segunda página lista os jogadores seguintes e deixa de fixar o estudante que está nela; um professor que oculta um estudante continua na mesma página; a tabela de relatórios mostra a segunda página; a busca por nome encontra um estudante |
block_playerhud_diagnostics.feature |
3 | O administrador encontra a página de uso e diagnóstico em Relatórios e vê os números; nada é apagado sem marcar a confirmação; confirmando, os dados deixados por um bloco removido são apagados e a cópia salva aparece na lista |
| Total | 80 |
php admin/tool/behat/cli/init.php
vendor/bin/behat --tags=@block_playerhud --profile=chrome