feat(admin): paginacao e filtragem no servidor, e edicao de utilizadores
As listas do painel respondiam com um LIMIT fixo e nenhum filtro: 200 caixas, 50 trabalhos na fila. Numa instalacao maior nao havia forma de chegar ao resto, e a fila so podia ser inspeccionada pela cabeca -- justamente quando esta acumulada e que alguem precisa de olhar para ela. - parsePageParams com testes proprios: pagina e tamanho tem de ser inteiros positivos, o tamanho e limitado, e um valor invalido cai no seguro em vez de chegar ao SQL como NaN, LIMIT negativo ou OFFSET fraccionario - o termo de pesquisa vai como parametro ligado, nunca interpolado; ha um teste que tenta terminar a instrucao e largar a tabela - a contagem usa o mesmo filtro da pagina, senao o numero de paginas descreve um conjunto de linhas diferente do que e mostrado - PATCH /admin/mailboxes/:id edita papel, quota, idioma, estado e liberta um bloqueio; so um super admin pode conceder super admin, ou um admin de dominio promovia-se para fora do seu proprio dominio Apelidos por utilizador: um apelido nao pertence a um utilizador, pertence ao dominio e leva uma lista de destinos que pode incluir enderecos noutros servidores, e um catch-all e do dominio inteiro. O que pode ser mostrado dentro de um utilizador sao os apelidos que entregam na caixa dele, e e isso que /admin/mailboxes/:id/aliases responde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
136f9f339a
commit
52bf43b74f
5 files changed
+551
-14
No files matched your search
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parsePageParams, MAX_PAGE_SIZE } from "./pagination.js";
|
||||
|
||||
const at = (qs: string) => new URL(`http://x/api${qs}`);
|
||||
|
||||
describe("pagination parameters", () => {
|
||||
it("defaults to the first page when nothing is asked for", () => {
|
||||
const p = parsePageParams(at(""));
|
||||
expect(p.page).toBe(1);
|
||||
expect(p.offset).toBe(0);
|
||||
expect(p.search).toBe("");
|
||||
});
|
||||
|
||||
it("turns a page number into the right offset", () => {
|
||||
const p = parsePageParams(at("?page=3&page_size=25"));
|
||||
expect(p.pageSize).toBe(25);
|
||||
expect(p.offset).toBe(50);
|
||||
});
|
||||
|
||||
// A page size taken at face value is a denial of service against your own
|
||||
// database: one request asking for ten million rows.
|
||||
it("caps the page size", () => {
|
||||
expect(parsePageParams(at("?page_size=100000")).pageSize).toBe(MAX_PAGE_SIZE);
|
||||
});
|
||||
|
||||
// Anything that is not a sane positive integer must land on the default
|
||||
// rather than reaching SQL as NaN, a negative LIMIT, or a fractional OFFSET.
|
||||
it.each(["0", "-5", "abc", "1.5", "", "1e9", "Infinity", "NaN"])(
|
||||
"falls back to a safe page for page=%s",
|
||||
(value) => {
|
||||
const p = parsePageParams(at(`?page=${value}`));
|
||||
expect(Number.isInteger(p.page)).toBe(true);
|
||||
expect(p.page).toBeGreaterThanOrEqual(1);
|
||||
expect(Number.isInteger(p.offset)).toBe(true);
|
||||
expect(p.offset).toBeGreaterThanOrEqual(0);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["0", "-5", "abc", "1.5", "NaN"])(
|
||||
"falls back to a safe page size for page_size=%s",
|
||||
(value) => {
|
||||
const p = parsePageParams(at(`?page_size=${value}`));
|
||||
expect(Number.isInteger(p.pageSize)).toBe(true);
|
||||
expect(p.pageSize).toBeGreaterThan(0);
|
||||
expect(p.pageSize).toBeLessThanOrEqual(MAX_PAGE_SIZE);
|
||||
},
|
||||
);
|
||||
|
||||
it("trims the search term and keeps it as plain text", () => {
|
||||
// It reaches SQL as a bound parameter, so the wildcards a user types are
|
||||
// theirs to type; what must not happen is it arriving untrimmed and
|
||||
// matching nothing.
|
||||
expect(parsePageParams(at("?search=%20%20ana%20%20")).search).toBe("ana");
|
||||
});
|
||||
|
||||
it("ignores an over-long search term instead of sending it to the database", () => {
|
||||
const p = parsePageParams(at(`?search=${"a".repeat(500)}`));
|
||||
expect(p.search.length).toBeLessThanOrEqual(200);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user