feat(inscription AM #120): UI étape pro, photo unifiée, scripts et données test

- Panneau AM : validation NIR alignée API, date d’agrément requise côté app,
  capacité 1–10, n° agrément + date sur une ligne, ville/pays formatés au blur.
- Widget RegistrationPhotoSlot (cadre, croix) partagé avec les cartes enfant.
- AuthService : MIME PNG/JPEG pour la photo ; payload date_agrement.
- Scripts register-am-dubois / mansouri ; chemins tests/ressources/photos ;
  doc test-data + seed ; smoke curl inscription AM.

Made-with: Cursor
This commit is contained in:
2026-04-13 13:19:32 +02:00
parent 12bf85b7bd
commit 58607cdbc9
18 changed files with 763 additions and 284 deletions
+127
View File
@@ -0,0 +1,127 @@
/**
* POST /api/v1/auth/register/am — jeu de test officiel Marie DUBOIS (docs/test-data + seed).
* Email : marie.dubois@ptits-pas.fr
*
* Photo attendue : tests/ressources/photos/dubois-marie.png
* Données alignées sur database/seed/03_seed_test_data.sql (NIR, dates, agrément, adresse).
* (réduction JPEG locale via sharp-cli si besoin, comme les autres scripts lourds)
*
* Usage : node tests/scripts/register-am-dubois-test.mjs [BASE_URL]
*/
import fs from 'fs';
import path from 'path';
import os from 'os';
import https from 'https';
import http from 'http';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, '..', '..');
const photosDir = path.join(__dirname, '..', 'ressources', 'photos');
function shrinkToJpeg(inputPath, label) {
const out = path.join(os.tmpdir(), `ptitspas-${label}-${Date.now()}.jpg`);
const cmd = `npx --yes sharp-cli -i ${JSON.stringify(inputPath)} -o ${JSON.stringify(out)} -mq80 resize 600 600`;
execSync(cmd, { cwd: repoRoot, stdio: 'inherit', shell: true });
return out;
}
function toDataUri(filePath) {
const buf = fs.readFileSync(filePath);
const ext = path.extname(filePath).toLowerCase();
const mime =
ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
return `data:${mime};base64,${buf.toString('base64')}`;
}
const photoSrc = path.join(photosDir, 'dubois-marie.png');
let photoPath;
try {
console.error('Préparation photo (sharp-cli)…');
photoPath = shrinkToJpeg(photoSrc, 'am-dubois');
const body = {
email: 'marie.dubois@ptits-pas.fr',
prenom: 'Marie',
nom: 'DUBOIS',
telephone: '0696345678',
adresse: '25 Rue de la République',
code_postal: '95870',
ville: 'Bezons',
photo_base64: toDataUri(photoPath),
photo_filename: 'marie_dubois.jpg',
consentement_photo: true,
date_naissance: '1980-06-08',
lieu_naissance_ville: 'Ajaccio',
lieu_naissance_pays: 'France',
nir: '280062A00100191',
numero_agrement: 'AGR-2019-095001',
date_agrement: '2019-09-01',
capacite_accueil: 4,
biographie:
"Assistante maternelle agréée depuis 2019. Née en Corse à Ajaccio. Spécialité bébés 0-18 mois. " +
'Accueil bienveillant et cadre sécurisant. 2 places disponibles.',
acceptation_cgu: true,
acceptation_privacy: true,
};
const json = JSON.stringify(body);
const baseArg = process.argv[2] || 'https://app.ptits-pas.fr';
const base = new URL(baseArg.endsWith('/') ? baseArg.slice(0, -1) : baseArg);
const url = new URL('/api/v1/auth/register/am', `${base.protocol}//${base.host}`);
const opts = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'Content-Length': Buffer.byteLength(json, 'utf8'),
},
};
const lib = url.protocol === 'https:' ? https : http;
console.error(`POST ${url.href} (payload ~${Math.round(json.length / 1024)} Ko)`);
const req = lib.request(opts, (res) => {
let data = '';
res.on('data', (c) => {
data += c;
});
res.on('end', () => {
console.log('HTTP', res.statusCode);
try {
const j = JSON.parse(data);
console.log(JSON.stringify(j, null, 2));
} catch {
console.log(data.slice(0, 4000));
}
});
});
req.on('error', (e) => {
console.error('Erreur réseau:', e.message);
process.exit(1);
});
req.setTimeout(120000, () => {
req.destroy();
console.error('Timeout 120s');
process.exit(1);
});
req.write(json);
req.end();
} finally {
try {
if (photoPath && fs.existsSync(photoPath)) fs.unlinkSync(photoPath);
} catch {
/* ignore */
}
}
+126
View File
@@ -0,0 +1,126 @@
/**
* POST /api/v1/auth/register/am — jeu de test officiel Fatima EL MANSOURI (docs/test-data + seed).
* Email : fatima.elmansouri@ptits-pas.fr
*
* Photo attendue : tests/ressources/photos/mansouri-fatima.png
* Données alignées sur database/seed/03_seed_test_data.sql (NIR, dates, agrément, adresse).
*
* Usage : node tests/scripts/register-am-mansouri-test.mjs [BASE_URL]
*/
import fs from 'fs';
import path from 'path';
import os from 'os';
import https from 'https';
import http from 'http';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, '..', '..');
const photosDir = path.join(__dirname, '..', 'ressources', 'photos');
function shrinkToJpeg(inputPath, label) {
const out = path.join(os.tmpdir(), `ptitspas-${label}-${Date.now()}.jpg`);
const cmd = `npx --yes sharp-cli -i ${JSON.stringify(inputPath)} -o ${JSON.stringify(out)} -mq80 resize 600 600`;
execSync(cmd, { cwd: repoRoot, stdio: 'inherit', shell: true });
return out;
}
function toDataUri(filePath) {
const buf = fs.readFileSync(filePath);
const ext = path.extname(filePath).toLowerCase();
const mime =
ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
return `data:${mime};base64,${buf.toString('base64')}`;
}
const photoSrc = path.join(photosDir, 'mansouri-fatima.png');
let photoPath;
try {
console.error('Préparation photo (sharp-cli)…');
photoPath = shrinkToJpeg(photoSrc, 'am-mansouri');
const body = {
email: 'fatima.elmansouri@ptits-pas.fr',
prenom: 'Fatima',
nom: 'EL MANSOURI',
telephone: '0675456789',
adresse: '17 Boulevard Aristide Briand',
code_postal: '95870',
ville: 'Bezons',
photo_base64: toDataUri(photoPath),
photo_filename: 'fatima_elmansouri.jpg',
consentement_photo: true,
date_naissance: '1975-11-12',
lieu_naissance_ville: 'Casablanca',
lieu_naissance_pays: 'Maroc',
nir: '275119900100102',
numero_agrement: 'AGR-2017-095002',
date_agrement: '2017-06-15',
capacite_accueil: 3,
biographie:
"Assistante maternelle expérimentée. Née à l'étranger. Spécialité 1-3 ans. " +
'Accueil à la journée. 1 place disponible.',
acceptation_cgu: true,
acceptation_privacy: true,
};
const json = JSON.stringify(body);
const baseArg = process.argv[2] || 'https://app.ptits-pas.fr';
const base = new URL(baseArg.endsWith('/') ? baseArg.slice(0, -1) : baseArg);
const url = new URL('/api/v1/auth/register/am', `${base.protocol}//${base.host}`);
const opts = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'Content-Length': Buffer.byteLength(json, 'utf8'),
},
};
const lib = url.protocol === 'https:' ? https : http;
console.error(`POST ${url.href} (payload ~${Math.round(json.length / 1024)} Ko)`);
const req = lib.request(opts, (res) => {
let data = '';
res.on('data', (c) => {
data += c;
});
res.on('end', () => {
console.log('HTTP', res.statusCode);
try {
const j = JSON.parse(data);
console.log(JSON.stringify(j, null, 2));
} catch {
console.log(data.slice(0, 4000));
}
});
});
req.on('error', (e) => {
console.error('Erreur réseau:', e.message);
process.exit(1);
});
req.setTimeout(120000, () => {
req.destroy();
console.error('Timeout 120s');
process.exit(1);
});
req.write(json);
req.end();
} finally {
try {
if (photoPath && fs.existsSync(photoPath)) fs.unlinkSync(photoPath);
} catch {
/* ignore */
}
}
@@ -2,7 +2,7 @@
* POST /api/v1/auth/register/parent — jeu de test officiel couple DURAND / ROUSSEAU (docs/test-data + seed).
* Emails : amelie.durand@ptits-pas.fr, julien.rousseau@ptits-pas.fr
*
* Les PNG dans ressources/Photos dépassent la limite JSON (~15 Mo) du serveur : réduction locale
* Les PNG dans tests/ressources/photos dépassent la limite JSON (~15 Mo) du serveur : réduction locale
* via npx sharp-cli (resize 600 + JPEG q80) avant encodage base64.
*
* Usage : node tests/scripts/register-parent-durand-rousseau-test.mjs [BASE_URL]
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, '..', '..');
const photosDir = path.join(repoRoot, 'ressources', 'Photos');
const photosDir = path.join(__dirname, '..', 'ressources', 'photos');
function shrinkToJpeg(inputPath, label) {
const out = path.join(os.tmpdir(), `ptitspas-${label}-${Date.now()}.jpg`);
@@ -41,8 +41,8 @@ const presentationDossier =
"Nous recherchons une assistante maternelle à Bezons pour accueillir nos enfants dans un cadre bienveillant et stable. " +
"Merci pour l'étude de notre dossier.";
const chloeSrc = path.join(photosDir, 'G_Chloé.png');
const hugoSrc = path.join(photosDir, 'G_Hugo.png');
const chloeSrc = path.join(photosDir, 'rousseau-chloe.png');
const hugoSrc = path.join(photosDir, 'rousseau-hugo.png');
let chloeJpg;
let hugoJpg;
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, '..', '..');
const photosDir = path.join(repoRoot, 'ressources', 'Photos');
const photosDir = path.join(__dirname, '..', 'ressources', 'photos');
function toDataUri(filePath) {
const buf = fs.readFileSync(filePath);
@@ -40,7 +40,7 @@ const body = {
nom: 'LECOMTE',
date_naissance: '2023-04-15',
genre: 'H',
photo_base64: toDataUri(path.join(photosDir, 'C_Maxime.png')),
photo_base64: toDataUri(path.join(photosDir, 'lecomte-maxime.png')),
photo_filename: 'maxime_lecomte.png',
grossesse_multiple: false,
},
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.join(__dirname, '..', '..');
const photosDir = path.join(repoRoot, 'ressources', 'Photos');
const photosDir = path.join(__dirname, '..', 'ressources', 'photos');
function toDataUri(filePath) {
const buf = fs.readFileSync(filePath);
@@ -46,7 +46,7 @@ const body = {
nom: 'MARTIN',
date_naissance: '2023-02-15',
genre: 'F',
photo_base64: toDataUri(path.join(photosDir, 'C_Emma MARTIN.png')),
photo_base64: toDataUri(path.join(photosDir, 'martin-emma.png')),
photo_filename: 'emma_martin.png',
grossesse_multiple: true,
},
@@ -55,7 +55,7 @@ const body = {
nom: 'MARTIN',
date_naissance: '2023-02-15',
genre: 'H',
photo_base64: toDataUri(path.join(photosDir, 'C_Noah MARTIN_2.png')),
photo_base64: toDataUri(path.join(photosDir, 'martin-noah.png')),
photo_filename: 'noah_martin.png',
grossesse_multiple: true,
},
@@ -64,7 +64,7 @@ const body = {
nom: 'MARTIN',
date_naissance: '2023-02-15',
genre: 'F',
photo_base64: toDataUri(path.join(photosDir, 'C_Léa MMARTIN.png')),
photo_base64: toDataUri(path.join(photosDir, 'martin-lea.png')),
photo_filename: 'lea_martin.png',
grossesse_multiple: true,
},