Add scouthub-attivit-be

This commit is contained in:
Lorenzo Sanesi
2026-07-23 19:34:00 +02:00
parent 904cd2ee9b
commit 2ee7d402b6
55 changed files with 9783 additions and 0 deletions
@@ -0,0 +1,192 @@
import request from 'supertest';
import nock from 'nock';
import { app } from '../../src/app';
import { prisma } from '../../src/db/prisma';
import { resetDatabase } from '../setup/resetDatabase';
import { bearer, mockKeycloakJwks } from '../setup/authTestHelper';
const AUTORE = { sub: 'user-movio', email: 'movio@example.com', name: 'Movio' };
const ALTRO_AUTORE = { sub: 'user-altro', email: 'altro@example.com', name: 'Altro Utente' };
const AUTH_HEADER = bearer(AUTORE);
const ALTRO_AUTH_HEADER = bearer(ALTRO_AUTORE);
function attivitaPayload(overrides: Record<string, unknown> = {}) {
return {
nome: 'Caccia al tesoro',
stato: { id: 'BO', nome: 'Bozza' },
brancaList: [{ nome: 'E/G' }],
categoriaList: [{ nome: 'grande gioco' }],
materialeList: [],
periodoAnnoList: [],
paragrafoList: [{ corpo: 'Introduzione al gioco', tipo: { id: 'PARAGRAFO' }, ordine: 1 }],
...overrides,
};
}
beforeAll(() => {
mockKeycloakJwks();
});
afterAll(() => {
nock.cleanAll();
});
beforeEach(async () => {
await resetDatabase();
});
afterEach(async () => {
await resetDatabase();
});
describe('attivita endpoints', () => {
test('GET /health risponde 200 con { status: "ok" }', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.body).toEqual({ status: 'ok' });
});
test('GET /public/attivita/get/lista/home su DB vuoto restituisce []', async () => {
const res = await request(app).get('/public/attivita/get/lista/home');
expect(res.status).toBe(200);
expect(res.body).toEqual([]);
});
test('GET /public/attivita/get/one/:id con id non numerico restituisce 400', async () => {
const res = await request(app).get('/public/attivita/get/one/abc');
expect(res.status).toBe(400);
});
test('GET /public/attivita/get/one/:id con id inesistente restituisce 404', async () => {
const res = await request(app).get('/public/attivita/get/one/999999');
expect(res.status).toBe(404);
});
test('endpoint privati senza token restituiscono 401', async () => {
const saveRes = await request(app).post('/private/attivita/save').send(attivitaPayload());
expect(saveRes.status).toBe(401);
const myRes = await request(app).get('/private/attivita/get/lista/my');
expect(myRes.status).toBe(401);
const changeRes = await request(app).get('/private/attivita/change/stato/1/PU');
expect(changeRes.status).toBe(401);
});
test('POST /private/attivita/save senza nome restituisce 400 con informazioni di validazione', async () => {
const { nome, ...payloadSenzaNome } = attivitaPayload();
const res = await request(app)
.post('/private/attivita/save')
.set('Authorization', AUTH_HEADER)
.send(payloadSenzaNome);
expect(res.status).toBe(400);
expect(typeof res.body.message).toBe('string');
expect(res.body.message.length).toBeGreaterThan(0);
expect(res.body.message).toContain('nome');
});
test("POST /private/attivita/save con payload valido crea l'attività, visibile poi via get/lista/my", async () => {
const saveRes = await request(app)
.post('/private/attivita/save')
.set('Authorization', AUTH_HEADER)
.send(attivitaPayload());
expect([200, 204]).toContain(saveRes.status);
const myRes = await request(app)
.get('/private/attivita/get/lista/my')
.set('Authorization', AUTH_HEADER);
expect(myRes.status).toBe(200);
expect(myRes.body).toHaveLength(1);
expect(myRes.body[0]).toMatchObject({ nome: 'Caccia al tesoro', autore: 'Movio' });
expect(myRes.body[0].brancaList.map((b: { nome: string }) => b.nome)).toEqual(['E/G']);
expect(myRes.body[0].categoriaList.map((c: { nome: string }) => c.nome)).toEqual([
'grande gioco',
]);
});
test('get/lista/my non restituisce le attività di un altro autore', async () => {
await request(app).post('/private/attivita/save').set('Authorization', AUTH_HEADER).send(attivitaPayload());
const myRes = await request(app)
.get('/private/attivita/get/lista/my')
.set('Authorization', ALTRO_AUTH_HEADER);
expect(myRes.status).toBe(200);
expect(myRes.body).toEqual([]);
});
test("POST /private/attivita/save su un'attività di un altro autore restituisce 403", async () => {
await request(app).post('/private/attivita/save').set('Authorization', AUTH_HEADER).send(attivitaPayload());
const created = await prisma.attivita.findFirstOrThrow();
const res = await request(app)
.post('/private/attivita/save')
.set('Authorization', ALTRO_AUTH_HEADER)
.send(attivitaPayload({ id: created.id, nome: 'Modificata da altri' }));
expect(res.status).toBe(403);
});
test("change/stato aggiorna lo stato e rende visibile l'attività in home", async () => {
await request(app).post('/private/attivita/save').set('Authorization', AUTH_HEADER).send(attivitaPayload());
const created = await prisma.attivita.findFirstOrThrow();
const changeRes = await request(app)
.get(`/private/attivita/change/stato/${created.id}/PU`)
.set('Authorization', AUTH_HEADER);
expect(changeRes.status).toBe(200);
expect(changeRes.body).toEqual({ id: 'PU', nome: 'Pubblicato' });
const homeRes = await request(app).get('/public/attivita/get/lista/home');
expect(homeRes.status).toBe(200);
expect(homeRes.body.map((a: { id: number }) => a.id)).toContain(created.id);
});
test('change/stato su id inesistente restituisce 404', async () => {
const res = await request(app)
.get('/private/attivita/change/stato/999999/PU')
.set('Authorization', AUTH_HEADER);
expect(res.status).toBe(404);
});
test("change/stato su un'attività di un altro autore restituisce 403", async () => {
await request(app).post('/private/attivita/save').set('Authorization', AUTH_HEADER).send(attivitaPayload());
const created = await prisma.attivita.findFirstOrThrow();
const res = await request(app)
.get(`/private/attivita/change/stato/${created.id}/PU`)
.set('Authorization', ALTRO_AUTH_HEADER);
expect(res.status).toBe(403);
});
test('CORS: risponde con Access-Control-Allow-Origin per un Origin autorizzato', async () => {
const res = await request(app).get('/health').set('Origin', 'http://localhost:4200');
expect(res.headers['access-control-allow-origin']).toBe('http://localhost:4200');
});
test('POST /public/attivita/get/lista/search con [] restituisce tutte le attività presenti', async () => {
await request(app)
.post('/private/attivita/save')
.set('Authorization', AUTH_HEADER)
.send(attivitaPayload({ nome: 'Prima' }));
await request(app)
.post('/private/attivita/save')
.set('Authorization', AUTH_HEADER)
.send(attivitaPayload({ nome: 'Seconda', stato: { id: 'PU', nome: 'Pubblicato' } }));
const res = await request(app).post('/public/attivita/get/lista/search').send([]);
expect(res.status).toBe(200);
expect(res.body).toHaveLength(2);
expect(res.body.map((a: { nome: string }) => a.nome).sort()).toEqual(['Prima', 'Seconda']);
});
});
@@ -0,0 +1,52 @@
import request from 'supertest';
import { app } from '../../src/app';
import { prisma } from '../../src/db/prisma';
// L'unica tabella che questi endpoint (read-only sulle anagrafiche) potrebbero
// trovare "sporca" e' `materiale` (il seed non ne inserisce nessuno): la
// puliamo prima di ogni test per non dipendere dall'ordine di esecuzione
// rispetto alle altre suite.
beforeEach(async () => {
await prisma.materiale.deleteMany();
});
describe('autocomplete endpoints', () => {
test('POST /public/autocomplete/get/search senza body contiene il gruppo Testo', async () => {
const res = await request(app).post('/public/autocomplete/get/search');
expect(res.status).toBe(200);
expect(res.body.some((g: { label: string }) => g.label === 'Testo')).toBe(true);
});
test('POST /public/autocomplete/get/search con "gioco" contiene Categoria con le sotto-categorie', async () => {
const res = await request(app)
.post('/public/autocomplete/get/search')
.set('Content-Type', 'application/json')
.send('"gioco"');
expect(res.status).toBe(200);
const categoria = res.body.find((g: { label: string }) => g.label === 'Categoria');
expect(categoria).toBeDefined();
expect(categoria.objectsList.map((o: { nome: string }) => o.nome).sort()).toEqual(
["gioco", "gioco d'acqua", 'gioco giungla', 'gioco notturno', 'grande gioco'].sort(),
);
});
test('POST /public/autocomplete/get/branca con "e/g" restituisce un solo elemento E/G', async () => {
const res = await request(app)
.post('/public/autocomplete/get/branca')
.set('Content-Type', 'application/json')
.send('"e/g"');
expect(res.status).toBe(200);
expect(res.body).toHaveLength(1);
expect(res.body[0]).toMatchObject({ nome: 'E/G', gruppo: 'branca' });
});
test('POST /public/autocomplete/get/materiale senza body restituisce array vuoto', async () => {
const res = await request(app).post('/public/autocomplete/get/materiale');
expect(res.status).toBe(200);
expect(res.body).toEqual([]);
});
});