Files
SeConectai_Vendas/test/vendas-dashboards.e2e-spec.ts
T
tiagoandClaude Sonnet 5 7daaa16935 Etapa 6 — Vendas, parcelas e dashboards
Adiciona o módulo sales: vendas com itens/parcelas em transação, transições
de status controladas (orçamento→confirmada→concluída, cancelamento em
cascata pras parcelas), edição só em orçamento, recebimentos (marcar-pago,
atualizar-atrasadas) e dashboards semanal/mensal/financeiro do tenant.

Corrige uma falha de isolamento entre tenants nas views de dashboard
(rodavam com privilégio do dono, ignorando RLS) via
security_invoker = true nas views, coberta por teste e2e dedicado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 22:07:00 -03:00

729 lines
26 KiB
TypeScript

import { randomUUID } from 'node:crypto';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module';
import { PrismaService } from '../src/config/prisma.service';
import { hashPassword } from '../src/modules/auth/password.util';
interface TokenPair {
access_token: string;
refresh_token: string;
}
describe('Painel Tenant — Vendas, Parcelas e Dashboards (e2e)', () => {
let app: INestApplication;
let prisma: PrismaService;
let httpServer: Parameters<typeof request>[0];
const senha = 'SenhaForteDemo#26';
const suffix = randomUUID().slice(0, 8);
const masterEmail = `master-etapa6-${suffix}@teste.com`;
let masterId: string;
let masterToken: string;
const tenantIds: string[] = [];
const planoIds: string[] = [];
const tenantUserIds: string[] = [];
async function authMaster(): Promise<string> {
const res = await request(httpServer)
.post('/auth/master/login')
.send({ email: masterEmail, senha })
.expect(200);
return (res.body as TokenPair).access_token;
}
function asMaster() {
return {
post: (url: string) =>
request(httpServer).post(url).set('Authorization', `Bearer ${masterToken}`),
};
}
function asTenant(token: string) {
return {
get: (url: string) => request(httpServer).get(url).set('Authorization', `Bearer ${token}`),
post: (url: string) => request(httpServer).post(url).set('Authorization', `Bearer ${token}`),
put: (url: string) => request(httpServer).put(url).set('Authorization', `Bearer ${token}`),
patch: (url: string) =>
request(httpServer).patch(url).set('Authorization', `Bearer ${token}`),
delete: (url: string) =>
request(httpServer).delete(url).set('Authorization', `Bearer ${token}`),
};
}
async function criarPlano(overrides: Partial<Record<string, unknown>> = {}) {
const res = await asMaster()
.post('/master/planos')
.send({
nome: `Plano ${suffix} ${randomUUID().slice(0, 4)}`,
valor_mensal: 199.9,
limite_instancias_whatsapp: 1,
...overrides,
})
.expect(201);
planoIds.push((res.body as { id: string }).id);
return res.body as { id: string };
}
async function criarTenantComAdmin() {
const plano = await criarPlano();
const emailAdmin = `admin-${randomUUID().slice(0, 8)}-${suffix}@teste.com`;
const res = await asMaster()
.post('/master/tenants')
.send({
razao_social: `Tenant Etapa6 ${suffix} ${randomUUID().slice(0, 6)}`,
email_contato: `contato-${randomUUID().slice(0, 6)}@teste.com`,
plano_id: plano.id,
admin: { nome: 'Admin de Teste', email: emailAdmin },
})
.expect(201);
const body = res.body as {
tenant: { id: string };
admin: { id: string; email: string; senha_provisoria: string };
};
tenantIds.push(body.tenant.id);
tenantUserIds.push(body.admin.id);
await prisma.tenants.update({ where: { id: body.tenant.id }, data: { status: 'ativo' } });
const login = await request(httpServer)
.post('/auth/tenant/login')
.send({ email: body.admin.email, senha: body.admin.senha_provisoria })
.expect(200);
return { tenant: body.tenant, token: (login.body as TokenPair).access_token };
}
async function criarCustomer(token: string, overrides: Partial<Record<string, unknown>> = {}) {
const res = await asTenant(token)
.post('/tenant/customers')
.send({ razao_social: `Cliente ${randomUUID().slice(0, 8)}`, ...overrides })
.expect(201);
return res.body as { id: string };
}
async function criarProduto(token: string, overrides: Partial<Record<string, unknown>> = {}) {
const res = await asTenant(token)
.post('/tenant/produtos')
.send({
tipo: 'produto',
nome: `Produto ${randomUUID().slice(0, 8)}`,
valor_venda: 100,
...overrides,
})
.expect(201);
return res.body as { id: string };
}
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }),
);
await app.init();
httpServer = app.getHttpServer() as Parameters<typeof request>[0];
prisma = app.get(PrismaService);
const senha_hash = await hashPassword(senha);
const master = await prisma.master_admins.create({
data: { nome: 'Master Etapa 6', email: masterEmail, senha_hash },
});
masterId = master.id;
masterToken = await authMaster();
});
afterAll(async () => {
// `sale_items.product_service_id`/`sale_payments` não têm CASCADE de
// `products_services`/`sales` no schema (de propósito — histórico de
// vendas não pode sumir só porque o produto foi excluído). Sem apagar
// `sales` primeiro, o cascade de `tenants` → `products_services` pode
// rodar antes do cascade `tenants` → `sales` → `sale_items` e violar a
// FK. Apagar `sales` explicitamente já cascateia pra `sale_items`/
// `sale_payments` (essas duas têm `ON DELETE CASCADE` em `sale_id`).
await prisma.sales.deleteMany({ where: { tenant_id: { in: tenantIds } } });
await prisma.tenants.deleteMany({ where: { id: { in: tenantIds } } });
await prisma.planos.deleteMany({ where: { id: { in: planoIds } } });
await prisma.auth_refresh_tokens.deleteMany({
where: { usuario_id: { in: [masterId, ...tenantUserIds] } },
});
await prisma.login_attempts.deleteMany({
where: { usuario_tipo: 'master', email: masterEmail },
});
await prisma.audit_logs.deleteMany({ where: { usuario_id: masterId } });
await prisma.master_admins.deleteMany({ where: { id: masterId } });
await app.close();
});
describe('POST /tenant/vendas — criação com itens e parcelas em transação', () => {
let token: string;
let customerId: string;
let produtoId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 50 })).id;
});
it('cria venda com numero_parcelas — divide o valor_total igualmente', async () => {
const res = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 3 }],
numero_parcelas: 3,
})
.expect(201);
const venda = res.body as {
valor_total: string;
itens: Array<{ valor_total: string }>;
parcelas: Array<{ valor: string; numero_parcela: number }>;
};
expect(Number(venda.valor_total)).toBe(150);
expect(venda.parcelas).toHaveLength(3);
expect(venda.parcelas.reduce((acc, p) => acc + Number(p.valor), 0)).toBeCloseTo(150, 2);
});
it('cria venda com parcelas explícitas cuja soma bate com valor_total', async () => {
const res = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 2, valor_unitario: 40 }],
parcelas: [
{ valor: 40, vencimento: '2026-09-01' },
{ valor: 40, vencimento: '2026-10-01' },
],
})
.expect(201);
expect(Number((res.body as { valor_total: string }).valor_total)).toBe(80);
});
it('desconto aplicado reduz o valor_total', async () => {
const res = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 2, valor_unitario: 50 }],
desconto: 20,
numero_parcelas: 1,
})
.expect(201);
expect(Number((res.body as { valor_total: string }).valor_total)).toBe(80);
});
it('soma das parcelas não bate com valor_total retorna 400', async () => {
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 100 }],
parcelas: [{ valor: 50, vencimento: '2026-09-01' }],
})
.expect(400);
});
it('desconto maior que a soma dos itens retorna 400', async () => {
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 100 }],
desconto: 200,
numero_parcelas: 1,
})
.expect(400);
});
it('numero_parcelas e parcelas juntos retorna 400', async () => {
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 100 }],
numero_parcelas: 2,
parcelas: [{ valor: 100, vencimento: '2026-09-01' }],
})
.expect(400);
});
it('nem numero_parcelas nem parcelas retorna 400', async () => {
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 100 }],
})
.expect(400);
});
it('customer_id inexistente retorna 400', async () => {
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: randomUUID(),
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 100 }],
numero_parcelas: 1,
})
.expect(400);
});
it('product_service_id inexistente retorna 400 e não cria nada (transação revertida)', async () => {
const antes = await asTenant(token).get('/tenant/vendas?limit=1').expect(200);
const totalAntes = (antes.body as { meta: { total: number } }).meta.total;
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [
{ product_service_id: produtoId, quantidade: 1, valor_unitario: 10 },
{ product_service_id: randomUUID(), quantidade: 1, valor_unitario: 10 },
],
numero_parcelas: 1,
})
.expect(400);
const depois = await asTenant(token).get('/tenant/vendas?limit=1').expect(200);
expect((depois.body as { meta: { total: number } }).meta.total).toBe(totalAntes);
});
it('produto inativo retorna 400', async () => {
const produtoInativoId = (await criarProduto(token)).id;
await asTenant(token).delete(`/tenant/produtos/${produtoInativoId}`).expect(200);
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoInativoId, quantidade: 1, valor_unitario: 10 }],
numero_parcelas: 1,
})
.expect(400);
});
});
describe('GET /tenant/vendas — listagem e detalhe', () => {
let token: string;
let customerId: string;
let produtoId: string;
let vendaId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 30 })).id;
const venda = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
vendaId = (venda.body as { id: string }).id;
});
it('lista com filtro por customer_id e status', async () => {
const res = await asTenant(token)
.get(`/tenant/vendas?customer_id=${customerId}&status=confirmada`)
.expect(200);
const body = res.body as { data: Array<{ id: string }> };
expect(body.data.some((v) => v.id === vendaId)).toBe(true);
});
it('detalhe traz itens e parcelas', async () => {
const res = await asTenant(token).get(`/tenant/vendas/${vendaId}`).expect(200);
const venda = res.body as { itens: unknown[]; parcelas: unknown[] };
expect(venda.itens.length).toBe(1);
expect(venda.parcelas.length).toBe(1);
});
it('venda inexistente retorna 404', async () => {
await asTenant(token).get(`/tenant/vendas/${randomUUID()}`).expect(404);
});
});
describe('PATCH /tenant/vendas/:id/status — transições e cancelamento em cascata', () => {
let token: string;
let customerId: string;
let produtoId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 60 })).id;
});
async function criarVenda(status?: string) {
const res = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 2,
...(status ? { status } : {}),
})
.expect(201);
return res.body as { id: string };
}
it('orcamento → confirmada → concluida funciona', async () => {
const venda = await criarVenda('orcamento');
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'confirmada' })
.expect(200);
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'concluida' })
.expect(200);
});
it('orcamento → concluida direto é transição inválida (409)', async () => {
const venda = await criarVenda('orcamento');
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'concluida' })
.expect(409);
});
it('cancelar venda cancela as parcelas pendentes associadas', async () => {
const venda = await criarVenda();
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'cancelada' })
.expect(200);
const detalhe = await asTenant(token).get(`/tenant/vendas/${venda.id}`).expect(200);
const parcelas = (detalhe.body as { parcelas: Array<{ status: string }> }).parcelas;
expect(parcelas.every((p) => p.status === 'cancelado')).toBe(true);
});
it('venda já cancelada não aceita nova transição (409)', async () => {
const venda = await criarVenda();
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'cancelada' })
.expect(200);
await asTenant(token)
.patch(`/tenant/vendas/${venda.id}/status`)
.send({ status: 'confirmada' })
.expect(409);
});
});
describe('PUT /tenant/vendas/:id — só edita venda em orcamento', () => {
let token: string;
let customerId: string;
let produtoId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 25 })).id;
});
it('edita itens/valores de uma venda em orcamento', async () => {
const criada = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
status: 'orcamento',
})
.expect(201);
const id = (criada.body as { id: string }).id;
const editada = await asTenant(token)
.put(`/tenant/vendas/${id}`)
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 4 }],
numero_parcelas: 2,
})
.expect(200);
expect(Number((editada.body as { valor_total: string }).valor_total)).toBe(100);
expect((editada.body as { parcelas: unknown[] }).parcelas).toHaveLength(2);
});
it('venda confirmada não pode ser editada via PUT (409)', async () => {
const criada = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
const id = (criada.body as { id: string }).id;
await asTenant(token)
.put(`/tenant/vendas/${id}`)
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 2 }],
numero_parcelas: 1,
})
.expect(409);
});
});
describe('recebimentos — listagem, marcar-pago e atualizar-atrasadas', () => {
let token: string;
let customerId: string;
let produtoId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 90 })).id;
});
it('marca parcela como paga — pago_em e forma_pagamento preenchidos', async () => {
const venda = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
const parcelaId = (venda.body as { parcelas: Array<{ id: string }> }).parcelas[0].id;
const pago = await asTenant(token)
.patch(`/tenant/recebimentos/${parcelaId}/marcar-pago`)
.send({ forma_pagamento: 'pix' })
.expect(200);
const body = pago.body as { status: string; pago_em: string | null; forma_pagamento: string };
expect(body.status).toBe('pago');
expect(body.pago_em).not.toBeNull();
expect(body.forma_pagamento).toBe('pix');
});
it('marcar como paga uma parcela já paga retorna 409', async () => {
const venda = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
const parcelaId = (venda.body as { parcelas: Array<{ id: string }> }).parcelas[0].id;
await asTenant(token)
.patch(`/tenant/recebimentos/${parcelaId}/marcar-pago`)
.send({ forma_pagamento: 'pix' })
.expect(200);
await asTenant(token)
.patch(`/tenant/recebimentos/${parcelaId}/marcar-pago`)
.send({ forma_pagamento: 'pix' })
.expect(409);
});
it('atualizar-atrasadas muda parcela pendente vencida para atrasado', async () => {
const venda = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 90 }],
parcelas: [{ valor: 90, vencimento: '2020-01-01' }],
})
.expect(201);
const parcelaId = (venda.body as { parcelas: Array<{ id: string }> }).parcelas[0].id;
await asTenant(token).post('/tenant/recebimentos/atualizar-atrasadas').expect(201);
const lista = await asTenant(token).get(`/tenant/recebimentos?status=atrasado`).expect(200);
const ids = (lista.body as { data: Array<{ id: string }> }).data.map((p) => p.id);
expect(ids).toContain(parcelaId);
});
it('lista recebimentos filtrando por customer_id', async () => {
const res = await asTenant(token)
.get(`/tenant/recebimentos?customer_id=${customerId}`)
.expect(200);
const body = res.body as { data: Array<{ sale: { customer_id: string } }> };
expect(body.data.every((p) => p.sale.customer_id === customerId)).toBe(true);
});
});
describe('dashboard do tenant', () => {
let token: string;
let customerId: string;
let produtoId: string;
beforeAll(async () => {
const tenant = await criarTenantComAdmin();
token = tenant.token;
customerId = (await criarCustomer(token)).id;
produtoId = (await criarProduto(token, { valor_venda: 200 })).id;
// venda confirmada (conta nos resumos) e uma em orçamento (não deve contar)
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1 }],
numero_parcelas: 1,
status: 'orcamento',
})
.expect(201);
});
it('resumo-mensal inclui a venda confirmada do mês corrente', async () => {
const res = await asTenant(token).get('/tenant/dashboard/resumo-mensal').expect(200);
const linhas = res.body as Array<{ qtd_vendas: number; total_vendido: number }>;
expect(linhas.length).toBe(1);
expect(linhas[0].qtd_vendas).toBe(1);
expect(linhas[0].total_vendido).toBe(200);
});
it('resumo-semanal inclui a venda confirmada da semana corrente', async () => {
const res = await asTenant(token).get('/tenant/dashboard/resumo-semanal').expect(200);
const linhas = res.body as Array<{ qtd_vendas: number }>;
expect(linhas.length).toBe(1);
expect(linhas[0].qtd_vendas).toBe(1);
});
it('financeiro reflete parcela paga no total recebido do mês', async () => {
const antes = await asTenant(token).get('/tenant/dashboard/financeiro').expect(200);
const totalAntes = Number((antes.body as { total_recebido_mes: string }).total_recebido_mes);
const vendaComPendencia = await asTenant(token)
.post('/tenant/vendas')
.send({
customer_id: customerId,
itens: [{ product_service_id: produtoId, quantidade: 1, valor_unitario: 200 }],
numero_parcelas: 1,
})
.expect(201);
const parcelaId = (vendaComPendencia.body as { parcelas: Array<{ id: string }> }).parcelas[0]
.id;
await asTenant(token)
.patch(`/tenant/recebimentos/${parcelaId}/marcar-pago`)
.send({ forma_pagamento: 'dinheiro' })
.expect(200);
const depois = await asTenant(token).get('/tenant/dashboard/financeiro').expect(200);
const totalDepois = Number(
(depois.body as { total_recebido_mes: string }).total_recebido_mes,
);
expect(totalDepois - totalAntes).toBeCloseTo(200, 2);
});
});
describe('isolamento entre tenants (RLS) — reforçado nesta etapa', () => {
let tokenA: string;
let tokenB: string;
let vendaIdA: string;
let parcelaIdA: string;
beforeAll(async () => {
const tenantA = await criarTenantComAdmin();
tokenA = tenantA.token;
const tenantB = await criarTenantComAdmin();
tokenB = tenantB.token;
const customerA = await criarCustomer(tokenA);
const produtoA = await criarProduto(tokenA, { valor_venda: 333 });
const venda = await asTenant(tokenA)
.post('/tenant/vendas')
.send({
customer_id: customerA.id,
itens: [{ product_service_id: produtoA.id, quantidade: 1 }],
numero_parcelas: 1,
})
.expect(201);
vendaIdA = (venda.body as { id: string }).id;
parcelaIdA = (venda.body as { parcelas: Array<{ id: string }> }).parcelas[0].id;
});
it('tenant B não enxerga venda do tenant A (404, não 403)', async () => {
await asTenant(tokenB).get(`/tenant/vendas/${vendaIdA}`).expect(404);
await asTenant(tokenB)
.patch(`/tenant/vendas/${vendaIdA}/status`)
.send({ status: 'cancelada' })
.expect(404);
const lista = await asTenant(tokenB).get('/tenant/vendas').expect(200);
const ids = (lista.body as { data: Array<{ id: string }> }).data.map((v) => v.id);
expect(ids).not.toContain(vendaIdA);
});
it('tenant B não enxerga nem consegue marcar como paga a parcela do tenant A', async () => {
const lista = await asTenant(tokenB).get('/tenant/recebimentos').expect(200);
const ids = (lista.body as { data: Array<{ id: string }> }).data.map((p) => p.id);
expect(ids).not.toContain(parcelaIdA);
await asTenant(tokenB)
.patch(`/tenant/recebimentos/${parcelaIdA}/marcar-pago`)
.send({ forma_pagamento: 'pix' })
.expect(404);
});
it('dashboard do tenant B não inclui a venda do tenant A (confirma o fix de security_invoker das views)', async () => {
const res = await asTenant(tokenB).get('/tenant/dashboard/resumo-mensal').expect(200);
const linhas = res.body as Array<{ total_vendido: number }>;
expect(linhas.some((l) => l.total_vendido === 333)).toBe(false);
});
});
describe('acesso master sem contexto de tenant', () => {
it('master "puro" recebe 403 nos endpoints desta etapa', async () => {
await request(httpServer)
.get('/tenant/vendas')
.set('Authorization', `Bearer ${masterToken}`)
.expect(403);
await request(httpServer)
.get('/tenant/recebimentos')
.set('Authorization', `Bearer ${masterToken}`)
.expect(403);
await request(httpServer)
.get('/tenant/dashboard/financeiro')
.set('Authorization', `Bearer ${masterToken}`)
.expect(403);
});
});
});