test: add 5 multi-turn conversation examples

Scripted, deterministic conversations (fictional Swiss e-learning chats) via a
mock provider, each asserting exact per-turn round-trips + stable entity ids:
login support (attributes attach to PER_1), two learners (applyKnown reuse of a
re-mentioned name), old/new IBAN kept distinct, parent+minor+doctor (3 people),
and HR (manager+org reused, two employees). 46 tests total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mobiletic
2026-07-01 14:36:36 +01:00
parent 5ed8001101
commit d119c366b7

View File

@@ -295,3 +295,169 @@ describe('multi-turn conversation (anonymizeTurn)', () => {
expect(session.history).toHaveLength(4); expect(session.history).toHaveLength(4);
}); });
}); });
/**
* Five multi-turn conversation examples (fictional Swiss e-learning chats), each
* driven by a deterministic scripted provider. Every turn must restore exactly,
* and entity ids must stay stable across the whole conversation.
*/
describe('conversation examples (multi-turn scenarios)', () => {
type Turn = { text: string; anon: string; mapping: Record<string, string> };
async function runConversation(turns: Turn[]): Promise<AnonymizerSession> {
const anonymizeInConversation = vi.fn();
for (const t of turns) {
anonymizeInConversation.mockResolvedValueOnce({ anon: t.anon, mapping: t.mapping, legend: {} });
}
const a = new Anonymizer({ llm: failingLlm({ anonymizeInConversation }) });
let session: AnonymizerSession = { mapping: {}, legend: {}, history: [] };
for (const t of turns) {
const r = await a.anonymizeTurn(t.text, session);
session = r.session;
expect(a.deanonymize(r.anon, r.mapping)).toBe(t.text); // exact round-trip, every turn
}
expect(session.history).toHaveLength(turns.length);
return session;
}
it('1 — login support: attributes attach to the same learner (PER_1) across turns', async () => {
const s = await runConversation([
{
text: "Bonjour, je suis Élodie Vogt et je n'arrive plus à me connecter.",
anon: "Bonjour, je suis [PER_1.PRENOM:F] [PER_1.NOM:F] et je n'arrive plus à me connecter.",
mapping: { '[PER_1.PRENOM:F]': 'Élodie', '[PER_1.NOM:F]': 'Vogt' },
},
{
text: 'Mon e-mail de compte est elodie.vogt@gmx.ch.',
anon: 'Mon e-mail de compte est [PER_1.EMAIL:Perso].',
mapping: { '[PER_1.EMAIL:Perso]': 'elodie.vogt@gmx.ch' },
},
{
text: 'Merci de réinitialiser mon mot de passe pour la formation « Comptabilité ».',
anon: 'Merci de réinitialiser mon mot de passe pour la formation « Comptabilité ».',
mapping: {},
},
]);
expect(s.mapping['[PER_1.PRENOM:F]']).toBe('Élodie');
expect(s.mapping['[PER_1.EMAIL:Perso]']).toBe('elodie.vogt@gmx.ch');
expect(Object.keys(s.mapping).some((k) => k.startsWith('[PER_2'))).toBe(false); // only one person
});
it('2 — two learners: a friend introduced then re-mentioned keeps PER_2 (applyKnown reuse)', async () => {
const s = await runConversation([
{
text: "Salut, c'est Idris Haldimann. Je veux m'inscrire à la formation « Développement web ».",
anon: "Salut, c'est [PER_1.PRENOM:M] [PER_1.NOM:M]. Je veux m'inscrire à la formation « Développement web ».",
mapping: { '[PER_1.PRENOM:M]': 'Idris', '[PER_1.NOM:M]': 'Haldimann' },
},
{
text: 'Mon amie Léa Bianchi aimerait aussi participer.',
anon: 'Mon amie [PER_2.PRENOM:F] [PER_2.NOM:F] aimerait aussi participer.',
mapping: { '[PER_2.PRENOM:F]': 'Léa', '[PER_2.NOM:F]': 'Bianchi' },
},
{
// "Léa" is re-mentioned literally → applyKnown reuses [PER_2.PRENOM:F] before the model.
text: "Léa a déjà un compte sous l'e-mail lea.bianchi@bluewin.ch.",
anon: "[PER_2.PRENOM:F] a déjà un compte sous l'e-mail [PER_2.EMAIL:Perso].",
mapping: { '[PER_2.EMAIL:Perso]': 'lea.bianchi@bluewin.ch' },
},
]);
expect(s.mapping['[PER_1.PRENOM:M]']).toBe('Idris');
expect(s.mapping['[PER_2.PRENOM:F]']).toBe('Léa'); // stable across turns 2 & 3
expect(s.mapping['[PER_2.EMAIL:Perso]']).toBe('lea.bianchi@bluewin.ch');
});
it('3 — billing: old vs new IBAN kept distinct under one person', async () => {
const s = await runConversation([
{
text: "Bonjour, ici Bruno Keller, j'ai un souci de facturation.",
anon: "Bonjour, ici [PER_1.PRENOM:M] [PER_1.NOM:M], j'ai un souci de facturation.",
mapping: { '[PER_1.PRENOM:M]': 'Bruno', '[PER_1.NOM:M]': 'Keller' },
},
{
text: "Mon ancien IBAN CH51 0483 5012 3456 7800 9 n'est plus valide.",
anon: "Mon ancien IBAN [PER_1.IBAN:Ancien] n'est plus valide.",
mapping: { '[PER_1.IBAN:Ancien]': 'CH51 0483 5012 3456 7800 9' },
},
{
text: 'Le nouveau est CH33 0900 0000 8765 4321 0, merci.',
anon: 'Le nouveau est [PER_1.IBAN:Nouveau], merci.',
mapping: { '[PER_1.IBAN:Nouveau]': 'CH33 0900 0000 8765 4321 0' },
},
]);
expect(s.mapping['[PER_1.IBAN:Ancien]']).toBe('CH51 0483 5012 3456 7800 9');
expect(s.mapping['[PER_1.IBAN:Nouveau]']).toBe('CH33 0900 0000 8765 4321 0');
});
it('4 — parent + minor child + doctor: three distinct people across turns', async () => {
const s = await runConversation([
{
text: "Bonjour, je souhaite inscrire mon fils Timéo Moret, né le 09.09.2012, à l'atelier « Robotique ».",
anon: "Bonjour, je souhaite inscrire mon fils [PER_1.PRENOM:M] [PER_1.NOM:M], né le [PER_1.DATE_NAISSANCE:2012], à l'atelier « Robotique ».",
mapping: {
'[PER_1.PRENOM:M]': 'Timéo',
'[PER_1.NOM:M]': 'Moret',
'[PER_1.DATE_NAISSANCE:2012]': '09.09.2012',
},
},
{
text: "C'est moi sa mère, Sandra Baumann, qui gère le dossier ; mon numéro est 079 222 33 44.",
anon: "C'est moi sa mère, [PER_2.PRENOM:F] [PER_2.NOM:F], qui gère le dossier ; mon numéro est [PER_2.TELEPHONE:Mobile].",
mapping: {
'[PER_2.PRENOM:F]': 'Sandra',
'[PER_2.NOM:F]': 'Baumann',
'[PER_2.TELEPHONE:Mobile]': '079 222 33 44',
},
},
{
text: 'Le certificat médical a été établi par le Dr Rui Almeida.',
anon: 'Le certificat médical a été établi par le Dr [PER_3.PRENOM:M] [PER_3.NOM:M].',
mapping: { '[PER_3.PRENOM:M]': 'Rui', '[PER_3.NOM:M]': 'Almeida' },
},
]);
expect(s.mapping['[PER_1.NOM:M]']).toBe('Moret'); // child
expect(s.mapping['[PER_2.NOM:F]']).toBe('Baumann'); // mother
expect(s.mapping['[PER_3.NOM:M]']).toBe('Almeida'); // doctor
});
it('5 — HR: manager + org reused, two employees added across turns', async () => {
const s = await runConversation([
{
text: 'Bonjour, je suis Patrick Nussbaum, RH chez Migros.',
anon: 'Bonjour, je suis [PER_1.PRENOM:M] [PER_1.NOM:M], RH chez [ORG_1.NOM:Entreprise].',
mapping: {
'[PER_1.PRENOM:M]': 'Patrick',
'[PER_1.NOM:M]': 'Nussbaum',
'[ORG_1.NOM:Entreprise]': 'Migros',
},
},
{
text: 'Je veux inscrire Chloé Aebischer (chloe.aebischer@migros.ch) à « Sécurité ».',
anon: 'Je veux inscrire [PER_2.PRENOM:F] [PER_2.NOM:F] ([PER_2.EMAIL:Pro]) à « Sécurité ».',
mapping: {
'[PER_2.PRENOM:F]': 'Chloé',
'[PER_2.NOM:F]': 'Aebischer',
'[PER_2.EMAIL:Pro]': 'chloe.aebischer@migros.ch',
},
},
{
text: 'Ainsi que Deniz Yilmaz, joignable au 076 555 88 99.',
anon: 'Ainsi que [PER_3.PRENOM:U] [PER_3.NOM:U], joignable au [PER_3.TELEPHONE:Mobile].',
mapping: {
'[PER_3.PRENOM:U]': 'Deniz',
'[PER_3.NOM:U]': 'Yilmaz',
'[PER_3.TELEPHONE:Mobile]': '076 555 88 99',
},
},
{
text: 'La facture va à notre IBAN CH70 0076 2011 6238 5295 7.',
anon: 'La facture va à notre IBAN [ORG_1.IBAN:Entreprise].',
mapping: { '[ORG_1.IBAN:Entreprise]': 'CH70 0076 2011 6238 5295 7' },
},
]);
expect(s.mapping['[ORG_1.NOM:Entreprise]']).toBe('Migros'); // org stable across turns 1 & 4
expect(s.mapping['[PER_2.PRENOM:F]']).toBe('Chloé');
expect(s.mapping['[PER_3.NOM:U]']).toBe('Yilmaz');
expect(s.mapping['[ORG_1.IBAN:Entreprise]']).toBe('CH70 0076 2011 6238 5295 7');
});
});