@mostajs/wagon-engine 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
  Format : Keep a Changelog ; versions : SemVer.
5
5
 
6
+ ## [0.1.1] — 2026-10-26
7
+
8
+ ### Corrigé (suite de conformité)
9
+ Speeking, première greffe qui ne ressemble pas au gabarit, a fait apparaître trois hypothèses fausses :
10
+ - **T-WAG-2** attendait une clé d'API à l'inscription, et un palier gratuit nommé « Gratuit ». La clé n'est attendue que si le palier gratuit en permet une (`maxCles > 0`), et le nom vient de la configuration.
11
+ - **T-WAG-5** (API par clé) est **ignoré avec son motif** quand le palier gratuit ne donne pas de clé.
12
+ - **T-WAG-6** montait la limite du *premier* palier payant à 777, ce qui inversait l'échelle quand un palier plus haut avait une limite plus basse (« Classe » 40, « École » 300). Il prend maintenant le palier payant à la plus grande limite finie, et le porte au-dessus de toutes les autres.
13
+
14
+ Une seconde greffe d'essai, faite comme une école (`test-scripts/greffe-plafond`), rejoue la suite à chaque version.
15
+
6
16
  ## [0.1.0] — 2026-10-26
7
17
 
8
18
  ### Extrait
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/wagon-engine",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Le wagon d'un SaaS, une fois pour toutes : comptes, connexion, paliers, quotas, paiement (Stripe, Chargily, SATIM), espace client, administration, pages légales, fr/en/ar. Une greffe n'apporte que wagon.config.mjs, charge/, legal/ et son .env ; la suite de conformité publiée juge la greffe.",
5
5
  "keywords": [
6
6
  "mostajs",
@@ -39,6 +39,13 @@ const ESSAI = charge.conformite?.route ? charge.conformite : null;
39
39
  /** `conformite.tarifs: false` : la charge remplace /tarifs (ex. les prix de ses voyages) — les paliers n'y sont pas. */
40
40
  const TARIFS = charge.conformite?.tarifs !== false;
41
41
  const MAIL_PAYANT = 'payant@exemple.test';
42
+ /** 0.1.1 — une clé d'API naît-elle à l'inscription ? Seulement si le palier gratuit en permet une (maxCles > 0). */
43
+ const CLE_A_L_INSCRIPTION = Number(GRATUIT?.limits?.maxCles ?? 0) > 0;
44
+ /** 0.1.1 — T-WAG-6 change une limite SANS inverser l'échelle : le palier payant qui a la plus grande limite finie, porté
45
+ * au-dessus de toutes les autres (777 au moins). Le premier palier payant n'est pas toujours le plus haut. */
46
+ const finie = (p) => Number(p.limits?.[LIMITE]) >= 0;
47
+ const CIBLE = ACTIFS.filter((p) => Number(p.price) > 0 && finie(p)).sort((a, b) => Number(b.limits[LIMITE]) - Number(a.limits[LIMITE]))[0] ?? null;
48
+ const VALEUR_ADMIN = String(Math.max(777, ...ACTIFS.filter(finie).map((p) => Number(p.limits[LIMITE]) + 1)));
42
49
  /** Un essai sans objet pour cette greffe : ignoré AVEC son motif (mjs-unit ≥ 0.8 : il entre au rapport et au compte rendu). */
43
50
  const sansObjet = (motif) => (nom, fn) => test.skip(nom, fn, `sans objet : ${motif}`);
44
51
 
@@ -133,8 +140,8 @@ test('T-WAG-2 — inscription, connexion, espace, déconnexion', async () => {
133
140
  const r = await inscrire(B, 'anis@exemple.test');
134
141
  expect(r.status).toBe(200);
135
142
  const m = r.texte.match(/sk_(?:test|live)_[0-9a-f]{64}/);
136
- expect(m !== null).toBe(true);
137
- cleApi = m[0];
143
+ expect(m !== null).toBe(CLE_A_L_INSCRIPTION); // pas de clé si le palier gratuit n'en donne pas
144
+ cleApi = m?.[0] ?? '';
138
145
  expect(r.cookie !== '').toBe(true);
139
146
  const deja = await inscrire(B, 'anis@exemple.test');
140
147
  expect(/existe déjà/i.test(deja.texte)).toBe(true);
@@ -142,9 +149,8 @@ test('T-WAG-2 — inscription, connexion, espace, déconnexion', async () => {
142
149
  const c = await connecter(B, 'anis@exemple.test');
143
150
  expect(c !== '').toBe(true);
144
151
  const e = (await req(B, '/compte', { cookie: c })).texte;
145
- expect(e.includes('Gratuit')).toBe(true);
146
- expect(e.includes(cleApi.slice(0, 12))).toBe(true);
147
- expect(e.includes(cleApi)).toBe(false);
152
+ expect(e.includes(GRATUIT.name)).toBe(true);
153
+ if (CLE_A_L_INSCRIPTION) { expect(e.includes(cleApi.slice(0, 12))).toBe(true); expect(e.includes(cleApi)).toBe(false); }
148
154
  const d = await req(B, '/deconnexion', { method: 'POST', cookie: c, form: {} }); // 0.5 : un POST
149
155
  expect(d.status).toBe(303);
150
156
  expect((await req(B, '/compte', { cookie: c })).status).toBe(303);
@@ -238,7 +244,7 @@ test('T-WAG-2 — inscription, connexion, espace, déconnexion', async () => {
238
244
  expect(appels.sessions.length).toBe(avant); // aucune session Stripe ouverte
239
245
  });
240
246
 
241
- test('T-WAG-5 — API par clé, révocation bornée', async () => {
247
+ (CLE_A_L_INSCRIPTION ? test : sansObjet('le palier gratuit ne donne pas de clé d’API (maxCles 0)'))(`T-WAG-5 — API par clé, révocation bornée${CLE_A_L_INSCRIPTION ? '' : ' (sans objet : pas de clé d’API au palier gratuit)'}`, async () => {
242
248
  const moi = await req(B, '/api/v1/moi', { headers: { authorization: `Bearer ${cleApi}` } });
243
249
  expect(moi.status).toBe(200);
244
250
  expect(JSON.parse(moi.texte).compte.nom).toBe('Société anis@exemple.test');
@@ -251,7 +257,7 @@ test('T-WAG-5 — API par clé, révocation bornée', async () => {
251
257
  expect((await req(B, '/api/v1/moi', { headers: { authorization: `Bearer ${cleApi}` } })).status).toBe(401);
252
258
  });
253
259
 
254
- (PAYANT && LIMITE ? test : sansObjet('pas de palier payant ou pas de limite'))(`T-WAG-6 — administration : accès, paliers, échelle, usage${PAYANT && LIMITE ? '' : ' (sans objet : pas de palier payant ou pas de limite)'}`, async () => {
260
+ (CIBLE && LIMITE ? test : sansObjet('pas de palier payant à limite finie, ou pas de limite'))(`T-WAG-6 — administration : accès, paliers, échelle, usage${CIBLE && LIMITE ? '' : ' (sans objet : pas de palier payant à limite finie, ou pas de limite)'}`, async () => {
255
261
  const client = await connecter(B, 'anis@exemple.test');
256
262
  expect((await req(B, '/admin/plans', { cookie: client })).status).toBe(404);
257
263
  await inscrire(B, 'admin@exemple.test');
@@ -260,12 +266,12 @@ test('T-WAG-5 — API par clé, révocation bornée', async () => {
260
266
  await A.w.confirmerAdmin('admin@exemple.test');
261
267
  const page = await req(B, '/admin/plans', { cookie: adm });
262
268
  expect(page.status).toBe(200);
263
- expect(page.texte.includes(`name="${PAYANT.slug}.${LIMITE}"`)).toBe(true);
269
+ expect(page.texte.includes(`name="${CIBLE.slug}.${LIMITE}"`)).toBe(true);
264
270
  const valeurs = Object.fromEntries(config.paliers.flatMap((p) => Object.entries(p.limits).map(([k, v]) => [`${p.slug}.${k}`, String(v)])));
265
- const ok = await req(B, '/admin/plans', { method: 'POST', cookie: adm, form: { ...valeurs, [`${PAYANT.slug}.${LIMITE}`]: '777' } });
271
+ const ok = await req(B, '/admin/plans', { method: 'POST', cookie: adm, form: { ...valeurs, [`${CIBLE.slug}.${LIMITE}`]: VALEUR_ADMIN } });
266
272
  expect(ok.status).toBe(200);
267
- if (TARIFS) expect((await req(B, '/tarifs')).texte.includes('777')).toBe(true);
268
- else expect((await req(B, '/admin/plans', { cookie: adm })).texte.includes('value="777"')).toBe(true); // la valeur tient, vue de l'administration
273
+ if (TARIFS) expect((await req(B, '/tarifs')).texte.includes(VALEUR_ADMIN)).toBe(true);
274
+ else expect((await req(B, '/admin/plans', { cookie: adm })).texte.includes(`value="${VALEUR_ADMIN}"`)).toBe(true); // la valeur tient, vue de l'administration
269
275
  // L'échelle inversée : le gratuit donnerait PLUS que le payant.
270
276
  const inverse = await req(B, '/admin/plans', { method: 'POST', cookie: adm, form: { ...valeurs, [`${GRATUIT.slug}.${LIMITE}`]: '2', [`${PAYANT.slug}.${LIMITE}`]: '1' } });
271
277
  expect(/inversée|moins/i.test(inverse.texte)).toBe(true);