Qualidade: testes, segurança e manual
Uma aplicação a sério não é só «funcionar no meu computador». Hoje testas o teu CRM com método, proteges o CRM contra um ataque muito comum e escreves o manual do utilizador.
🎯 No fim desta aula vais conseguir
- Escrever e executar um plano de testes (o que faço → o que espero → o que aconteceu).
- Explicar o que é a injeção de HTML e proteger o CRM com uma função
limpar(). - Testar o CRM no modo telemóvel do browser e deixar a Consola sem erros.
- Escrever o manual do utilizador (
README.md) em Markdown.
🧰 Antes de começar
- Abre a pasta
meu-crmno VS Code e oindex.htmlcom o Live Server. - Faz uma cópia de segurança: comprime a pasta
meu-crmnum zip com o nomemeu-crm-antes-aula18.zip. - Tem à mão o teu anteprojeto (PDF ou papel): vais precisar dele para os testes e para o manual.
😕 Não há ponto de controlo da aula 17: cada CRM agora é diferente! Se perdeste o teu projeto, parte do ponto de controlo da aula 16 e refaz as personalizações com a ajuda do anteprojeto (e fala com o professor).
💡 Os números de linha indicados nesta página são os do ponto de controlo da aula 16. No teu ficheiro podem ser outros: usa Ctrl+F no VS Code para procurar o texto indicado.
Parte A — Testar com método (≈ 30 min)
1 O que é um plano de testes
Nas empresas de software há pessoas cujo trabalho é testar (chamam-se testadores ou QA, de quality assurance). Não testam «a ver se funciona»: seguem um plano de testes, uma tabela em que cada linha diz o que faço, o que espero que aconteça e o que aconteceu de facto.
O segredo está na ordem: o resultado esperado escreve-se antes de testar. Assim não te deixas enganar por «bem, apareceu qualquer coisa, deve estar certo».
Um teste que falha não é uma vergonha: é um erro que encontraste tu, em vez de ser o cliente a encontrá-lo.
2 Criar o ficheiro do plano de testes
Vamos escrever o plano num ficheiro de texto com o formato Markdown (.md), que é o que o GitHub usa (aula 19). Numa tabela de Markdown, as colunas separam-se com |.
No VS Code, na lista de ficheiros à esquerda, clica com o botão direito num espaço vazio da pasta meu-crm → Novo Ficheiro… (New File…) → escreve TESTES.md → Enter. Cola o modelo:
Grava e carrega em Ctrl+Shift+V para veres a pré-visualização: a tabela aparece com linhas e colunas.
Troca o texto entre [ ] e escreve os testes 17 e 18 sobre as tuas personalizações (ex.: «Crio um sócio com o plano Anual → A coluna Plano mostra Anual e, ao editar, a lista mostra Anual»).
😕 A pré-visualização não mostra uma tabela
- Cada linha da tabela tem de começar e acabar com
|e ter o mesmo número de colunas (5). - Tem de haver uma linha em branco antes da tabela (a seguir à linha «Testado por…»).
- O ficheiro tem de terminar em
.md(e não.md.txt).
3 Executar os testes
Faz cada teste pela ordem, com o CRM no browser e o TESTES.md aberto no VS Code. Em cada linha, escreve na coluna «Resultado obtido» o que aconteceu (curto) e na coluna «OK?» ✅ ou ❌.
- Se um teste falhar, escreve o que aconteceu, tenta corrigir o erro e repete o teste. Se não conseguires corrigir hoje, escreve «por corrigir».
- Usa só dados inventados (ex.: «Teste Silva»). No fim podes apagar os clientes de teste.
✅ Deves ver: na pré-visualização, a tabela com as 18 linhas preenchidas. O ideal é estar tudo ✅ — mas um ❌ bem descrito também vale pontos.
Parte B — Segurança: a injeção de HTML (≈ 30 min)
4 O ataque
Lembras-te do innerHTML (aula 7)? Ele pega num texto e trata-o como código HTML. Na tabela, o nome do cliente vai para dentro do innerHTML… e quem escreve o nome é o utilizador. E se o utilizador escrever código HTML em vez de um nome?
🤔 Prevê: se criares um cliente com o nome <b>Ana</b>, o que vai aparecer na tabela?
Cria um cliente com este nome (copia-o para o campo Nome e guarda):
Agora cria outro cliente com este nome:
✅ Deves ver: no 1.º teste, «Ana» aparece a negrito (o browser obedeceu ao <b>). No 2.º, aparece uma janela a dizer «Fui atacado!» — e volta a aparecer sempre que a tabela é desenhada (experimenta F5). Pode aparecer mais do que uma vez.
O que aconteceu? O browser tentou carregar uma imagem que não existe (src=x) e, como deu erro, executou o código do onerror. Ou seja: um texto escrito num formulário conseguiu executar código no teu CRM. Aqui foi só um alert, mas num site a sério podia roubar dados, apagar registos ou enviar mensagens em nome do utilizador. Este ataque chama-se injeção de HTML (ou XSS — Cross-Site Scripting) e é um dos erros de segurança mais comuns na Web.
⚠️ Ética: faz estes testes só no teu próprio CRM. Experimentar ataques em sites de outras pessoas ou empresas é crime.
5 A defesa: a função limpar()
A defesa é trocar os caracteres especiais do HTML pelos seus códigos (chamados entidades): < passa a <, > passa a >, & passa a & e " passa a ". O browser mostra o sinal < no ecrã, mas já não o trata como o início de uma tag. É como pôr o texto dentro de uma montra: vê-se, mas não se mexe.
No app.js, procura (Ctrl+F) function linhaCliente. Por cima dela está o comentário // Cria o HTML de UMA linha da tabela para um cliente,. Põe o cursor na linha em branco que está por cima desse comentário (a seguir à chaveta } que fecha desenharTabela), carrega em Enter e cola:
Grava, recarrega o CRM e experimenta a função na Consola (F12):
🤔 Prevê: o que vai devolver limpar('<b>Olá</b>')? Escreve-o na Consola e carrega Enter.
✅ Deves ver na Consola: '<b>Olá</b>'. Ainda não muda nada na tabela: a função existe, mas ninguém a usa. Vamos tratar disso.
6 Usar limpar() na tabela de clientes
A regra é simples: todo o texto que veio do utilizador e vai para dentro de um innerHTML passa pelo limpar(). Em linhaCliente(), troca cada ${cliente.campo} por ${limpar(cliente.campo)}. Por exemplo, <td>${cliente.nome}</td> passa a <td>${limpar(cliente.nome)}</td>.
No ponto de controlo da aula 16, a função fica assim:
⚠️ Não substituas a tua função por esta! A tua linhaCliente tem as colunas que acrescentaste (Cidade, campos da aula 17, talvez o botão ⭐). Altera uma a uma as células da tua função. Nas colunas novas, o || '' deixa de ser preciso, porque o limpar() já transforma «nada» em texto vazio:
💡 Não mexas nos ${cliente.id} dos botões: o id é um número criado pelo próprio CRM (novoId()), não foi escrito pelo utilizador.
✅ Deves ver: depois de gravar e recarregar, o 1.º cliente de teste aparece como <b>Ana</b> (com os sinais, sem negrito) e o 2.º aparece como texto — se a janela «Fui atacado!» ainda aparecer, falta o passo 7.
7 Usar limpar() no resto do CRM
Há mais sítios onde texto do utilizador entra num innerHTML. Altera estas linhas (em cada uma, só muda o que está dentro de ${ }):
a) cartaoOportunidade() — o título da oportunidade e o nome do cliente:
b) desenharInteracoes() — a data, o tipo e a descrição de cada interação:
💡 Se fizeste o E1 da aula 13 (ícones), a tua linha tem ${iconeTipo(interacao.tipo)}: deixa-o como está (os ícones são escolhidos pelo teu código) e usa ${limpar(interacao.tipo)} só no texto do tipo.
c) preencherSelectClientes() — o nome do cliente na lista do formulário de oportunidades:
💡 Fizeste o desafio da aula 15 (Top 3)? Em desenharTop3(), usa também ${limpar(op.titulo)}.
Para confirmar, cria uma interação com a descrição <i>teste</i> e uma oportunidade com o título <u>teste</u> para o cliente «atacante».
✅ Deves ver: os textos aparecem tal e qual, com os sinais < e >, em todo o lado (tabela, ficha, quadro, lista de clientes das oportunidades), e a janela «Fui atacado!» nunca mais aparece, nem depois de F5. No fim, apaga os clientes de teste.
😕 Não funciona?
limpar is not defined→ a função não foi colada, não gravaste, ou o nome está diferente (ex.:Limpar).- A tabela desapareceu e a Consola mostra
SyntaxError→ falta um parêntese:${limpar(cliente.nome}em vez de${limpar(cliente.nome)}. - A janela «Fui atacado!» ainda aparece → há um sítio sem
limpar(). Procura (Ctrl+F)${cliente.nome}e${op.titulo}noapp.js. - Aparecem códigos como
<e>no ecrã (em vez dos sinais<e>) → o texto foi limpo duas vezes (limpar(limpar(...))), ou a função foi escrita à mão com os códigos errados. Volta a copiar o bloco do passo 5.
Parte C — Telemóvel, Consola e manual (≈ 35 min)
8 Testar no «telemóvel»
Muita gente vai abrir o teu CRM no telemóvel (sobretudo depois da aula 19). O browser consegue simular o ecrã de um telemóvel:
- Abre o DevTools (F12) e carrega em Ctrl+Shift+M (ou clica no ícone do telemóvel e tablet, no canto superior esquerdo do DevTools).
- Na barra que aparece por cima da página, escolhe um modelo (ex.: iPhone SE ou Samsung Galaxy S20 Ultra).
- Percorre o CRM todo: menu, formulário, tabela (desliza-a para o lado), ficha, quadro de oportunidades, painel e relatórios.
- Para voltar ao normal, carrega outra vez em Ctrl+Shift+M.
✅ Deves ver: o formulário numa só coluna, o menu a ocupar várias linhas e a tabela com deslizamento horizontal. Escreve o resultado no teste 16 do teu plano.
9 Consola sem erros
Uma aplicação pode «parecer» funcionar e ter erros escondidos. Abre a Consola (F12 → Consola), recarrega com Ctrl+F5 e usa todas as funcionalidades do CRM. Não deve aparecer nenhuma linha vermelha.
😕 Aparecem erros vermelhos
favicon.ico … 404 (Not Found)→ não é do teu código: o browser procura um ícone para o separador. Podes ignorar (ou resolver no «Se sobrar tempo»).… is not defined→ uma função ou variável com o nome mal escrito, ou que não existe. Clica no nome do ficheiro à direita do erro: o browser leva-te à linha.Cannot read properties of null→ umgetElementByIdcom umidque não existe noindex.html.- Se não conseguires perceber um erro, regista-o no plano de testes como «por corrigir» e pede ajuda ao professor (ou usa o pedido para a IA no fim da página).
10 O manual do utilizador: README.md
Um README («lê-me») é o primeiro ficheiro que alguém lê quando encontra um projeto: diz o que é, como se abre e como se usa. No GitHub, o README aparece automaticamente na página do projeto (aula 19). Escreve-se em Markdown, um formato de texto simples:
| Escreves | Aparece |
|---|---|
# Título | Título grande |
## Secção | Subtítulo |
- item | Lista com marcas |
1. passo | Lista numerada |
**importante** | importante |
`index.html` | texto em letra de código |
Cria o ficheiro README.md na pasta meu-crm (como fizeste com o TESTES.md) e cola o modelo. Depois, troca tudo o que está entre [ ] e adapta as secções ao teu CRM (os teus estados, os teus campos, os nomes que usaste):
Grava e vê a pré-visualização com Ctrl+Shift+V.
✅ Deves ver: um documento arrumado, com títulos, listas numeradas e texto a negrito, sem nenhum [ ] por preencher (exceto o link, que fica para a aula 19).
💡 Escreve o manual para alguém que nunca viu o teu CRM — por exemplo, a rececionista da tua empresa fictícia. Se ela não perceber uma frase, reescreve-a.
🔎 Investiga
- Porque é que, no
limpar(), o&tem de ser trocado primeiro? Muda a linha do¶ último lugar, grava e escrevelimpar('<')na Consola. O que dá? Volta a pôr como estava. - Antes de aplicares o
limpar(), abriste a ficha (👁️) do cliente «atacante»? O título da ficha mostrou o alerta? Procura emverCliente()como é escrito o nome. Que diferença há entretextContenteinnerHTML? - Na Consola, experimenta
limpar(null),limpar(42)elimpar('Pão & Companhia'). Para que servem o primeiroife oString()da função? - Pesquisa o que é o OWASP Top 10. Em que posição aparece a categoria Injection (injeção) na versão mais recente?
✍️ Tarefas do projeto (contam para a avaliação)
T1 · Plano de testes
O ficheiro TESTES.md tem pelo menos 12 testes executados, com o resultado obtido e ✅/❌ em todas as linhas. Pelo menos 2 testes são sobre as tuas personalizações.
💡 Um bom teste é concreto: «Escrevo o NIF 12345678» é melhor do que «Testo o NIF». O resultado esperado diz exatamente o que deve aparecer.
T2 · CRM protegido contra injeção de HTML
A função limpar() existe e é usada em linhaCliente (todas as células de texto, incluindo as tuas colunas novas), cartaoOportunidade, desenharInteracoes e preencherSelectClientes. O cliente <img src=x onerror=…> já não mostra o alerta.
💡 Pergunta a ti próprio, para cada ${…} do app.js: «este texto pode ter sido escrito por um utilizador?» Se sim, leva limpar().
T3 · Manual do utilizador
O README.md tem todas as secções do modelo preenchidas e adaptadas ao teu CRM: o que é, como abrir, como usar passo a passo, funcionalidades (com as tuas personalizações), onde ficam os dados e como fazer cópias, tecnologias e autor.
💡 Pede a um colega para ler o teu README sem ver o CRM e depois usar o CRM só com o que leu. Onde é que ele hesitou?
⭐ Desafio · Não deixar repetir o NIF
Numa base de dados a sério, o NIF de um cliente é único (em SQL diz-se UNIQUE). Faz com que o CRM não deixe guardar um cliente com um NIF que já pertence a outro cliente, mostrando uma mensagem de erro.
💡 Em guardarCliente(), depois da validação: percorre a lista clientes com um ciclo. Se encontrares um cliente com o mesmo NIF (e o NIF não estiver vazio), mostra o erro e sai com return. Cuidado: ao editar um cliente, o NIF dele próprio não conta como repetido (compara os id com Number(idEmEdicao)).
📦 O que mostrar ao professor
⏱️ Se sobrar tempo
- Faz o desafio ⭐ e acrescenta um teste ao teu plano para ele.
- Corrige os testes que ficaram «por corrigir».
- Dá um ícone ao separador do browser (e acaba com o erro do
favicon.ico): no<head>doindex.html, por baixo do<title>, acrescenta a linha abaixo e troca o emoji pelo do teu setor.
- Troca de CRM com um colega e testa o CRM dele com o teu plano de testes. Encontraste algum erro? Avisa-o!
🤖 Pedir ajuda à IA (opcional)
A IA é boa a rever textos. Pede-lhe que critique o teu manual, sem o reescrever. Copia o pedido, cola o teu README no fim e envia:
Tens um erro vermelho na Consola que não percebes? Usa o pedido da aula 7: a IA explica, tu corriges.
📖 Palavras novas
- Plano de testes
- Tabela com os testes a fazer: o que faço, o resultado esperado, o resultado obtido e se passou.
- Resultado esperado
- O que deve acontecer num teste. Escreve-se antes de testar.
- Bug
- Erro num programa (literalmente, «inseto»).
- Injeção de HTML (XSS)
- Ataque em que um texto escrito pelo utilizador é tratado como código pela página e executa ações sem autorização.
- Entidade HTML
- Código que representa um carácter especial, ex.:
<mostra<sem abrir uma tag. textContentvsinnerHTMLtextContentmostra sempre texto (seguro);innerHTMLinterpreta o texto como HTML (é preciso limpar o que vem do utilizador).- README
- Ficheiro «lê-me» com a explicação de um projeto: o que é, como se abre e como se usa.
- Markdown (
.md) - Formato de texto simples em que
#,-e**se transformam em títulos, listas e negrito. - Design responsivo
- Página que se adapta a ecrãs de tamanhos diferentes (computador, tablet, telemóvel).