3. Conformidade e instalação
O módulo avisos está escrito e instalado. Falta provar que ele cumpre o contrato do painel. O projeto traz um kit de conformidade que faz isso: o mesmo que cada módulo oficial roda nos testes dele.
Rodar o kit
Seção intitulada “Rodar o kit”O kit é a função runConformance(módulo, opções) de src/panel/testing/conformance.ts. Você a chama num arquivo de teste ao lado do módulo. O arquivo abaixo também guarda dois testes das regras do avisos, que o kit não conhece; eles aparecem mais adiante.
import { describe, expect, test } from "bun:test";import { runConformance } from "../../panel/testing/conformance";import { createTestApp, testUser } from "../../panel/testing/test-app";import { avisos } from "./module";
// The kit: manifest, host, permissions, collections and screens, over the memory Store and over SQLite.runConformance(avisos);
describe("avisos", () => { test("the public route lists only notices that are on and not expired", async () => { // Arrange const app = createTestApp({ modules: [avisos] }); const { store } = app.ports; await store.create("avisos", { titulo: "No ar", tipo: "info", ativo: true }); await store.create("avisos", { titulo: "Desligado", tipo: "info", ativo: false }); await store.create("avisos", { titulo: "Vencido", tipo: "info", ativo: true, validade: "2000-01-01" });
// Act const response = await app.request("/api/avisos/ativos");
// Assert expect(response.status).toBe(200); const body = (await response.json()) as { avisos: { titulo: string }[] }; expect(body.avisos.map((aviso) => aviso.titulo)).toEqual(["No ar"]); });
test("the hook refuses an urgent notice without a text", async () => { // Arrange const app = createTestApp({ modules: [avisos] }); const user = testUser(app.host, { role: "editor" });
// Act const response = await app.request("/painel/c/avisos", { method: "POST", user, form: { titulo: "Queda do sistema", tipo: "urgente" }, });
// Assert expect(response.status).toBe(422); expect(await response.text()).toContain("Um aviso urgente precisa de texto."); expect((await app.ports.store.list("avisos", {})).total).toBe(0); });});Rode só esse arquivo com bun test ./src/modules/avisos/module.test.ts. O bun run test do projeto o inclui, junto com os testes de todos os módulos.
runConformance registra o mesmo conjunto de verificações três vezes: com o armazenamento em memória e com cada banco de testTargets, que por padrão são sqlite://:memory: e um arquivo SQLite temporário. Quem passa nos três passa também no adaptador SQL real. Para apontar o kit para outro banco de testes, defina TEST_DATABASE_URL; o kit esvazia esse banco, então o endereço precisa conter test (ou você define TEST_DATABASE_ALLOW_WIPE=1). São dez verificações em cada passada, então o avisos roda 30 testes do kit, mais os 2 do arquivo: 32.
O que o kit confere
Seção intitulada “O que o kit confere”Os nomes abaixo são os que aparecem na saída dos testes.
| Verificação | O que ela faz |
|---|---|
manifesto válido | Passa o módulo por defineModule de novo. |
o host sobe com o módulo e suas dependências | Monta o host com o módulo e o que você passar em deps, no núcleo 1.0.0, e confere que o módulo ficou ativo. Pega faixa de núcleo que não cabe, dependência ausente, coleção, rota ou grupo de menu que outro módulo já usa e extensão que aponta para uma coleção que não existe. |
telas e rotas recusam quem não tem a permissão (403) | Para cada tela (GET e, se houver, POST) e cada rota com permission: quem tem a permissão não recebe 403; quem a perde recebe. Toda rota precisa ter permission ou public. |
módulo desativado: telas, rotas e coleções respondem 404 | Desliga o módulo e pede cada tela, rota e endereço de coleção: tudo responde 404. |
coleções: CRUD completo no Store | Cria pelo formulário gerado (303), confere que a lista linka o registro, edita (a versão sobe), salva com a versão velha (409) e exclui. |
coleções: cada ação recusa quem não tem a permissão (403) | Ler, criar, editar e excluir respondem 403 sem a permissão da ação. |
coleções: a edição exige leitura e, sem atualização, é só leitura | Sem leitura nem atualização, 403. Só com leitura, a página abre sem botão de salvar e com os campos travados (confere quando leitura e atualização são permissões diferentes, como no avisos). |
coleções: telas geradas desligadas respondem 404 | Uma tela gerada que você desligou, como em generated: { edit: false }, não existe, nem para quem tem todas as permissões. |
coleções: registro fora do escopo → 404 na página, fora da lista e da ação em massa | Vale só para coleção com scope. Exige a opção outOfScope. |
telas renderizam com um h1, ids únicos, rótulo em todo controle e csrf nos formulários | Abre cada tela do módulo e cada página gerada das coleções: um só h1, nenhum id repetido, todo campo com rótulo e todo formulário POST com o campo csrf. |
O kit preenche os formulários com exemplos genéricos por tipo de campo: Texto de teste num text, 2026-01-15 num date, a primeira opção num select. Uma regra sua que esses valores não cumprem faz o kit reprovar; veja a opção samples abaixo.
Opções do kit
Seção intitulada “Opções do kit”runConformance(módulo, opções) aceita:
| Opção | Para quê |
|---|---|
deps | Os módulos de que o seu depende (requires), em qualquer ordem. |
samples | Por coleção e campo, uma função (n) => texto que o kit usa no lugar do exemplo genérico, para campo com regra própria (um endereço, um código). n cresce a cada formulário, para valores que precisam ser únicos. |
outOfScope | Por coleção com scope, os dados que, postos sobre um exemplo, tiram o registro do escopo de um editor. Obrigatória para cada uma. |
scopeGrants | Por coleção com scope, permissões a ligar ou desligar no editor da verificação de escopo. |
coreVersion | A versão do núcleo em que o host sobe; a do template blog por padrão. |
mountCollections | O motor das coleções sob teste; o real por padrão. |
O módulo oficial redirecionamentos usa samples porque o campo de é um caminho e precisa ser único. Veja os módulos oficiais.
Quando o kit reprova
Seção intitulada “Quando o kit reprova”O kit para na primeira falha de cada verificação e diz o que viu. Dois casos que você pode provocar no avisos:
- Uma tela com formulário POST sem o campo
csrfreprova a última verificação, nos três passes:/painel/avisos/resumo: formulário POST sem o campo csrf (action=/painel/avisos/resumo). Ponha ocsrfdo contexto da tela num campo oculto. - Uma regra de gancho que o exemplo genérico viola, como recusar uma
validadeque já passou. O kit manda2026-01-15no campovalidade, e o formulário volta com 422:criar: POST /painel/c/avisos: esperava 303, veio 422 («Adicionar aviso · Painel Pular para o conteúdo…»). Quando a resposta é um erro, o kit cita só os primeiros 160 caracteres, que costumam ser o menu; para ver o motivo, poste os mesmos dados no painel. A saída é passar um valor válido emsamples, por exemplosamples: { avisos: { validade: () => "2099-12-31" } }. Duas verificações caem juntas, porque ambas criam um registro.
O que o kit não cobre
Seção intitulada “O que o kit não cobre”O kit confere o contrato, não o seu negócio. Ele não sabe o que a rota GET /api/avisos/ativos devolve, nem que o gancho recusa um aviso urgente sem texto. Os dois testes do arquivo acima cobrem isso, no padrão preparar, agir e conferir.
Uma migração roda nos bancos SQL do kit, que começam vazios. O kit mostra que ela não quebra, não que reescreve os registros certos. Se a migração mexe em dados, teste-a com um banco que tenha registros.
O que a 1.1.0 não faz
Seção intitulada “O que a 1.1.0 não faz”Instalar o módulo foi manual: copiar a pasta para src/modules/ e editar o índice. O comando slash module add não ajuda aqui. Ele só instala os módulos oficiais, os que o catálogo do slash-kit lista. Com avisos, o resultado é:
módulo desconhecido: avisos (disponíveis: categorias, comentarios, metricas, newsletter, paginas, posts, redirecionamentos, seo, tags)
O comando sai com código 1 e nada é copiado. Por isso:
slash module listmostra só o que está noslash.lock, e oavisosnão aparece. Ele fica fora do lock de propósito: um módulo que o catálogo não conhece no lock faz oslash module addrecusar tudo.- Não existe instalação de módulo de terceiros nem de módulo seu em outro projeto: você copia a pasta e edita o índice à mão, em cada projeto.
- Atualizar o módulo, em cada projeto, também é à mão. O
slash.locknão guarda a versão doavisos.
A lista completa do que a 1.1.0 não faz está em Limites da 1.1.0; os comandos, em referência do CLI.
O que você fez
Seção intitulada “O que você fez”Um módulo com coleção, campos, permissões, tela, rota pública, migração e gancho, instalado à mão e coberto pelo kit de conformidade e por testes das suas regras. O mesmo caminho serve para qualquer módulo seu. Os módulos oficiais mostram como o contrato escala: posts tem telas, rotas e ganchos de verdade.