<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{ site_name }}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"
integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF"
crossorigin="anonymous"></script>
<script>
window.axeptioSettings = {
clientId: "66137db6ae83de5e566cfc53",
cookiesVersion: "first-id sandbox-fr-EU",
};
(function (d, s) {
var t = d.getElementsByTagName(s)[0],
e = d.createElement(s);
e.async = false;
e.src = "//static.axept.io/tcf/sdk.js";
e.type = "module";
t.parentNode.insertBefore(e, t);
})(document, "script");
</script>
<style>
.test-card {
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
transition: background-color 0.3s ease;
}
.test-card.success {
background-color: #d4edda;
border-color: #28a745;
}
.test-card.error {
background-color: #f8d7da;
border-color: #dc3545;
}
.test-card.running {
background-color: #fff3cd;
border-color: #ffc107;
}
.test-card.disabled {
opacity: 0.5;
pointer-events: none;
}
.log-output {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
max-height: 300px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
}
.log-entry {
margin-bottom: 5px;
}
.log-entry.error {
color: #dc3545;
font-weight: bold;
}
.log-entry.debug {
color: #007bff;
}
.log-entry.info {
color: #28a745;
}
iframe {
width: 100%;
height: 400px;
border: 1px solid #dee2e6;
border-radius: 4px;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{{ path('app_index') }}">{{ site_name }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container mt-4">
<div class="row">
<div class="col-md-12">
<h1>Suite de Tests Complète</h1>
<p class="lead">Validation automatique de toutes les APIs FirstID</p>
<button id="startAllTests" class="btn btn-primary btn-lg mb-4">Démarrer tous les tests</button>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="test1" class="test-card">
<h3>Test 1: Premier chargement du SDK</h3>
<p>Suppression des cookies et chargement initial du SDK</p>
<div class="test-status">
<span class="badge badge-secondary" id="test1-status">En attente</span>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-primary" onclick="runTest1()">Exécuter Test 1</button>
</div>
<div class="log-output mt-3" id="test1-log"></div>
</div>
<div id="test2" class="test-card">
<h3>Test 2: Rechargement du SDK</h3>
<p>Rechargement du SDK sans suppression des cookies</p>
<div class="test-status">
<span class="badge badge-secondary" id="test2-status">En attente</span>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-primary" onclick="runTest2()">Exécuter Test 2</button>
<span class="badge badge-warning ml-2">Nécessite un first id (ou Test 1)</span>
</div>
<div class="log-output mt-3" id="test2-log"></div>
</div>
<div id="test3" class="test-card">
<h3>Test 3: Appels APP API</h3>
<p>Tests des appels APP API</p>
<div class="test-status">
<span class="badge badge-secondary" id="test3-status">En attente</span>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-primary" onclick="runTest3()">Exécuter Test 3</button>
</div>
<div class="log-output mt-3" id="test3-log"></div>
</div>
<div id="test4" class="test-card">
<h3>Test 4: Redirection Gate</h3>
<p>Test de la redirection vers la gate dans une iframe</p>
<div class="test-status">
<span class="badge badge-secondary" id="test4-status">En attente</span>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-primary" onclick="runTest4()">Exécuter Test 4</button>
</div>
<div class="log-output mt-3" id="test4-log"></div>
<div class="mt-3" id="test4-iframe-container"></div>
</div>
</div>
</div>
</div>
<script>
const COOKIES_TO_DELETE = ['firstid', 'firstid_flex_type', 'fid_email_hashed', 'fid_email_hash', 'fidsdk_consent', 'fid_customdata_hash', 'fidflexlastsync', 'fidflexemaillastsync', 'firstid_refresh', 'fidflexlastcheck'];
const SDK_LOAD_TIMEOUT_MS = 5000;
const FIRSTID_VENDOR_ID = 1178;
const MAID_MATCHING_API_PROXY_URL = '/test-suite/api-proxy';
let testState = {
currentTest: 0,
test1Passed: false,
test2Passed: false,
test3Passed: false,
test4Passed: false,
hasConsent: false
};
let originalConsoleDebug = console.debug;
let originalConsoleError = console.error;
let consoleInterceptActive = false;
let interceptedErrors = [];
function setupConsoleInterceptor(testNumber) {
interceptedErrors = [];
consoleInterceptActive = true;
console.debug = function(...args) {
if (consoleInterceptActive) {
logToTest(testNumber, 'debug', '[DEBUG] ' + args.join(' '));
}
originalConsoleDebug.apply(console, args);
};
console.error = function(...args) {
if (consoleInterceptActive) {
interceptedErrors.push(args.join(' '));
logToTest(testNumber, 'error', '[ERROR] ' + args.join(' '));
}
originalConsoleError.apply(console, args);
};
}
function teardownConsoleInterceptor() {
consoleInterceptActive = false;
console.debug = originalConsoleDebug;
console.error = originalConsoleError;
}
function logToTest(testNumber, level, message) {
const logElement = document.getElementById(`test${testNumber}-log`);
const entry = document.createElement('div');
entry.className = `log-entry ${level}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logElement.appendChild(entry);
logElement.scrollTop = logElement.scrollHeight;
}
function setTestStatus(testNumber, status, message) {
const statusElement = document.getElementById(`test${testNumber}-status`);
const testCard = document.getElementById(`test${testNumber}`);
testCard.classList.remove('running', 'success', 'error');
switch(status) {
case 'running':
statusElement.className = 'badge badge-warning';
statusElement.textContent = 'En cours...';
testCard.classList.add('running');
break;
case 'success':
statusElement.className = 'badge badge-success';
statusElement.textContent = 'Réussi';
testCard.classList.add('success');
break;
case 'error':
statusElement.className = 'badge badge-danger';
statusElement.textContent = 'Échec';
testCard.classList.add('error');
break;
case 'waiting':
statusElement.className = 'badge badge-secondary';
statusElement.textContent = 'En attente';
break;
}
if (message) {
logToTest(testNumber, status === 'error' ? 'error' : 'info', message);
}
}
function deleteCookies() {
const domain = window.location.hostname.split('.').slice(-2).join('.');
COOKIES_TO_DELETE.forEach(cookieName => {
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain}`;
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${domain}`;
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
});
return true;
}
function getCookieValue(cookieName) {
let cookieArr = document.cookie.split(";");
for (let i = 0; i < cookieArr.length; i++) {
let cookiePair = cookieArr[i].split("=");
if (cookieName === cookiePair[0].trim()) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
}
function loadFirstIdScript(testNumber) {
return new Promise((resolve, reject) => {
if (document.querySelector('script[src*="first-id.fr"]')) {
logToTest(testNumber, 'info', 'Script FirstID déjà chargé');
resolve();
return;
}
const script = document.createElement('script');
script.src = 'https://cdn.preprod.first-id.fr/sdk/loader/loader-latest-flex.js?id=1234567890';
script.defer = true;
script.onload = () => {
logToTest(testNumber, 'info', 'Script FirstID chargé avec succès');
resolve();
};
script.onerror = () => {
reject(new Error('Échec du chargement du script First-ID'));
};
document.head.appendChild(script);
});
}
function initializeFirstId(testNumber) {
window.firstId = window.firstId || {
debug: true,
cookieName: 'firstid',
firstIdFlexFeature: true,
callbacks: []
};
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Timeout FirstID après ${SDK_LOAD_TIMEOUT_MS}ms`));
}, SDK_LOAD_TIMEOUT_MS);
document.addEventListener('firstId:initialized', (event) => {
clearTimeout(timeout);
const firstId = event.detail.firstId;
logToTest(testNumber, 'info', `FirstID initialisé: ${firstId}`);
resolve(firstId);
}, {once: true});
});
}
async function runTest1() {
const testNumber = 1;
setTestStatus(testNumber, 'running', 'Démarrage du Test 1...');
setupConsoleInterceptor(testNumber);
try {
logToTest(testNumber, 'info', 'Suppression des cookies...');
deleteCookies();
logToTest(testNumber, 'info', 'Cookies supprimés');
logToTest(testNumber, 'info', 'Chargement du script FirstID...');
await loadFirstIdScript(testNumber);
logToTest(testNumber, 'info', 'Initialisation du SDK...');
const firstId = await initializeFirstId(testNumber);
if (!firstId) {
throw new Error('FirstID non obtenu après initialisation');
}
if (interceptedErrors.length > 0) {
throw new Error(`${interceptedErrors.length} erreur(s) console détectée(s)`);
}
logToTest(testNumber, 'info', `✓ FirstID obtenu: ${firstId}`);
logToTest(testNumber, 'info', '✓ Aucune erreur console détectée');
setTestStatus(testNumber, 'success', 'Test 1 réussi');
testState.test1Passed = true;
} catch (error) {
setTestStatus(testNumber, 'error', `Test 1 échoué: ${error.message}`);
testState.test1Passed = false;
} finally {
teardownConsoleInterceptor();
}
}
async function runTest2() {
if (!testState.test1Passed) {
logToTest(2, 'error', 'Le Test 1 doit être réussi avant de lancer le Test 2');
return;
}
const testNumber = 2;
setTestStatus(testNumber, 'running', 'Démarrage du Test 2...');
setupConsoleInterceptor(testNumber);
try {
logToTest(testNumber, 'info', 'Rechargement du SDK sans suppression des cookies...');
delete window.FIRSTID;
delete window.FIRSTID_BY_TYPE;
delete window.FIRSTID_LOADING;
const existingScripts = document.querySelectorAll('script[src*="first-id.fr"]');
existingScripts.forEach(script => script.remove());
logToTest(testNumber, 'info', 'Variables globales réinitialisées');
await loadFirstIdScript(testNumber);
logToTest(testNumber, 'info', 'Initialisation du SDK...');
const firstId = await initializeFirstId(testNumber);
if (!firstId) {
throw new Error('FirstID non obtenu après réinitialisation');
}
if (interceptedErrors.length > 0) {
throw new Error(`${interceptedErrors.length} erreur(s) console détectée(s)`);
}
logToTest(testNumber, 'info', `✓ FirstID obtenu: ${firstId}`);
logToTest(testNumber, 'info', '✓ Aucune erreur console détectée');
setTestStatus(testNumber, 'success', 'Test 2 réussi');
testState.test2Passed = true;
} catch (error) {
setTestStatus(testNumber, 'error', `Test 2 échoué: ${error.message}`);
testState.test2Passed = false;
} finally {
teardownConsoleInterceptor();
}
}
async function callMaidMatchingApi(payload) {
const response = await fetch(MAID_MATCHING_API_PROXY_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API call failed: ${response.status} - ${errorText}`);
}
return await response.json();
}
function generateMobileDeviceData(variant = 1) {
const devices = [
{
deviceType: 'mobile_test_suite',
deviceManufacturer: 'Apple',
deviceModel: 'iPhone 14 Pro',
osVersion: '17.2',
buildId: '21C5050',
deviceRam: 6000,
deviceResolution: '1179x2556',
appIdentifier: 'com.firstid.testapp'
},
{
deviceType: 'mobile_test_suite',
deviceManufacturer: 'Samsung',
deviceModel: 'Galaxy S23',
osVersion: '14',
buildId: 'TP1A.220624.014',
deviceRam: 8000,
deviceResolution: '1080x2340',
appIdentifier: 'com.firstid.testapp'
},
{
deviceType: 'mobile_test_suite',
deviceManufacturer: 'Google',
deviceModel: 'Pixel 8',
osVersion: '14',
buildId: 'UP1A.231105.003',
deviceRam: 8000,
deviceResolution: '1080x2400',
appIdentifier: 'com.firstid.testapp'
}
];
const randomDevice = devices[variant - 1] || devices[0];
return {
...randomDevice,
dfid: "true"
};
}
async function runTest3() {
const testNumber = 3;
setTestStatus(testNumber, 'running', 'Démarrage du Test 3...');
try {
logToTest(testNumber, 'info', '=== Test 3: Appels APP API Mobile ===');
logToTest(testNumber, 'info', 'Simulation d\'une application mobile avec appels API');
// Test 3.1: Premier appel sans firstId
logToTest(testNumber, 'info', '');
logToTest(testNumber, 'info', '--- Appel 1: Création initiale (sans firstId) ---');
const deviceData1 = generateMobileDeviceData(1);
const payload1 = {
...deviceData1,
maid: crypto.randomUUID(),
};
logToTest(testNumber, 'info', `Device: ${deviceData1.deviceManufacturer} ${deviceData1.deviceModel}`);
logToTest(testNumber, 'info', `MAID: ${payload1.maid}`);
logToTest(testNumber, 'info', 'Appel API en cours...');
const response1 = await callMaidMatchingApi(payload1);
if (!response1.firstId) {
throw new Error('Premier appel: firstId non reçu');
}
const obtainedFirstId = response1.firstId;
logToTest(testNumber, 'info', `✓ FirstID obtenu: ${obtainedFirstId}`);
if (response1.matchingStatus) {
logToTest(testNumber, 'info', ` Status: ${response1.matchingStatus}`);
}
// Pause entre les appels
await new Promise(resolve => setTimeout(resolve, 500));
// Test 3.2: Deuxième appel avec le firstId + données différentes
logToTest(testNumber, 'info', '');
logToTest(testNumber, 'info', '--- Appel 2: Avec firstId + données device différentes ---');
const deviceData2 = generateMobileDeviceData(2);
const payload2 = {
...deviceData2,
firstId: obtainedFirstId,
maid: crypto.randomUUID(),
};
logToTest(testNumber, 'info', `Device: ${deviceData2.deviceManufacturer} ${deviceData2.deviceModel}`);
logToTest(testNumber, 'info', `FirstID envoyé: ${obtainedFirstId}`);
logToTest(testNumber, 'info', `Nouveau MAID: ${payload2.maid}`);
logToTest(testNumber, 'info', 'Appel API en cours...');
const response2 = await callMaidMatchingApi(payload2);
if (!response2.firstId) {
throw new Error('Deuxième appel: firstId non reçu');
}
logToTest(testNumber, 'info', `✓ FirstID reçu: ${response2.firstId}`);
if (response2.matchingStatus) {
logToTest(testNumber, 'info', ` Status: ${response2.matchingStatus}`);
}
if (response2.firstId !== obtainedFirstId) {
logToTest(testNumber, 'error', `✗ ERREUR: FirstID différent! Attendu: ${obtainedFirstId}, Reçu: ${response2.firstId}`);
throw new Error('Le firstId retourné est différent de celui envoyé');
}
logToTest(testNumber, 'info', '✓ FirstID identique confirmé');
// Pause entre les appels
await new Promise(resolve => setTimeout(resolve, 500));
// Test 3.3: Troisième appel sans firstId mais avec les mêmes données que l'appel 2
logToTest(testNumber, 'info', '');
logToTest(testNumber, 'info', '--- Appel 3: Sans firstId avec mêmes données que appel 2 ---');
const payload3 = {
...deviceData2,
maid: payload2.maid,
};
logToTest(testNumber, 'info', `Device: ${deviceData2.deviceManufacturer} ${deviceData2.deviceModel} (identique appel 2)`);
logToTest(testNumber, 'info', `MAID: ${payload3.maid} (identique appel 2)`);
logToTest(testNumber, 'info', 'Appel API en cours...');
const response3 = await callMaidMatchingApi(payload3);
if (!response3.firstId) {
throw new Error('Troisième appel: firstId non reçu');
}
logToTest(testNumber, 'info', `✓ FirstID reçu: ${response3.firstId}`);
if (response3.matchingStatus) {
logToTest(testNumber, 'info', ` Status: ${response3.matchingStatus}`);
}
if (response3.firstId !== obtainedFirstId) {
logToTest(testNumber, 'error', `✗ ERREUR: FirstID différent! Attendu: ${obtainedFirstId}, Reçu: ${response3.firstId}`);
throw new Error('Le firstId retrouvé ne correspond pas au firstId initial');
}
logToTest(testNumber, 'info', '✓ FirstID retrouvé avec succès via MAID');
// Résumé
logToTest(testNumber, 'info', '');
logToTest(testNumber, 'info', '=== Résumé du Test 3 ===');
logToTest(testNumber, 'info', `✓ Appel 1: FirstID créé (${obtainedFirstId})`);
logToTest(testNumber, 'info', `✓ Appel 2: FirstID maintenu avec device différent`);
logToTest(testNumber, 'info', `✓ Appel 3: FirstID retrouvé via MAID matching`);
logToTest(testNumber, 'info', '✓ Tous les appels ont retourné le même firstId');
setTestStatus(testNumber, 'success', 'Test 3 réussi - API Mobile validée');
testState.test3Passed = true;
} catch (error) {
logToTest(testNumber, 'error', `Erreur détaillée: ${error.message}`);
if (error.stack) {
logToTest(testNumber, 'debug', error.stack);
}
setTestStatus(testNumber, 'error', `Test 3 échoué: ${error.message}`);
testState.test3Passed = false;
}
}
async function runTest4() {
const testNumber = 4;
setTestStatus(testNumber, 'running', 'Démarrage du Test 4...');
try {
logToTest(testNumber, 'info', 'Test de la redirection Gate...');
const iframeContainer = document.getElementById('test4-iframe-container');
iframeContainer.innerHTML = '';
const iframe = document.createElement('iframe');
const redirectHost = encodeURIComponent(window.location.origin + '/gate/handle-first-id-redirect');
const redirectUri = encodeURIComponent(window.location.origin + '/gate/handle-first-id-redirect');
iframe.src = `https://gate.preprod.first-id.fr?redirectHost=${redirectUri}&redirectUri=${redirectUri}`;
logToTest(testNumber, 'info', `URL Gate: ${iframe.src}`);
iframeContainer.appendChild(iframe);
iframe.onload = () => {
logToTest(testNumber, 'info', 'Iframe Gate chargée');
setTimeout(() => {
const firstIdCookie = getCookieValue('firstid');
if (firstIdCookie) {
logToTest(testNumber, 'info', `✓ Cookie firstid trouvé: ${firstIdCookie}`);
setTestStatus(testNumber, 'success', 'Test 4 réussi');
testState.test4Passed = true;
} else {
logToTest(testNumber, 'info', 'Cookie firstid non trouvé (vérification manuelle requise)');
setTestStatus(testNumber, 'success', 'Test 4 complété (vérification manuelle)');
}
}, 2000);
};
iframe.onerror = (error) => {
throw new Error('Erreur de chargement de l\'iframe Gate');
};
} catch (error) {
setTestStatus(testNumber, 'error', `Test 4 échoué: ${error.message}`);
testState.test4Passed = false;
}
}
function waitForConsent() {
return new Promise((resolve) => {
if (!window.__tcfapi) {
logToTest(0, 'info', 'TCF API non disponible, passage en mode sans consentement');
resolve(true);
return;
}
window.__tcfapi('addEventListener', 2, (tcData, success) => {
if (success && tcData.gdprApplies) {
if (tcData.vendor.consents[FIRSTID_VENDOR_ID] &&
(tcData.eventStatus === 'useractioncomplete' || tcData.eventStatus === 'tcloaded')) {
console.log('Consentement TCF obtenu');
testState.hasConsent = true;
resolve(true);
}
} else {
resolve(true);
}
});
});
}
document.getElementById('startAllTests').addEventListener('click', async function() {
this.disabled = true;
console.log('Attente du consentement TCF...');
await waitForConsent();
await runTest1();
if (testState.test1Passed) {
await new Promise(resolve => setTimeout(resolve, 1000));
await runTest2();
}
if (testState.test2Passed) {
await new Promise(resolve => setTimeout(resolve, 1000));
await runTest3();
}
if (testState.test3Passed) {
await new Promise(resolve => setTimeout(resolve, 1000));
await runTest4();
}
this.disabled = false;
});
</script>
</body>
</html>