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

🧰 Antes de começar

  1. Abre a pasta meu-crm no VS Code e o index.html com o Live Server.
  2. Faz uma cópia de segurança: comprime a pasta meu-crm num zip com o nome meu-crm-antes-aula18.zip.
  3. 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 ❌.

✅ 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 &lt;, > passa a &gt;, & passa a &amp; e " passa a &quot;. 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: '&lt;b&gt;Olá&lt;/b&gt;'. 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} no app.js.
  • Aparecem códigos como &lt; e &gt; 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:

  1. 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).
  2. Na barra que aparece por cima da página, escolhe um modelo (ex.: iPhone SE ou Samsung Galaxy S20 Ultra).
  3. Percorre o CRM todo: menu, formulário, tabela (desliza-a para o lado), ficha, quadro de oportunidades, painel e relatórios.
  4. 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 → um getElementById com um id que não existe no index.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:

EscrevesAparece
# TítuloTítulo grande
## SecçãoSubtítulo
- itemLista com marcas
1. passoLista 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

  1. Porque é que, no limpar(), o & tem de ser trocado primeiro? Muda a linha do & para último lugar, grava e escreve limpar('<') na Consola. O que dá? Volta a pôr como estava.
  2. Antes de aplicares o limpar(), abriste a ficha (👁️) do cliente «atacante»? O título da ficha mostrou o alerta? Procura em verCliente() como é escrito o nome. Que diferença há entre textContent e innerHTML?
  3. Na Consola, experimenta limpar(null), limpar(42) e limpar('Pão & Companhia'). Para que servem o primeiro if e o String() da função?
  4. 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

🤖 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.: &lt; mostra < sem abrir uma tag.
textContent vs innerHTML
textContent mostra sempre texto (seguro); innerHTML interpreta 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).