Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ e2e-customer-profile-local: ## Teste la fiche client dédiée (dépôt réel + s
e2e-pilotage-schema-local: ## Teste le schéma du pilotage (migration idempotente, nullabilité, contraintes uniques, sauvegarde/restauration) — copies temporaires, rien de réel touché
$(DC) run --rm wpcli wp eval "require ABSPATH . '$(THEME)/tools/e2e-pilotage-schema-local.php';" --user=admin

.PHONY: e2e-pilotage-perf-local
e2e-pilotage-perf-local: ## Teste la performance du registre des recettes sur un historique volumineux (20 000 lignes) — copie temporaire, index vérifié, rien de réel touché
$(DC) run --rm wpcli wp eval "require ABSPATH . '$(THEME)/tools/e2e-pilotage-perf-local.php';" --user=admin

.PHONY: e2e-address-lookup-local
e2e-address-lookup-local: ## Teste l'autocomplétion d'adresse (BAN, appels interceptés, endpoint câblé), rien d'écrit
$(DC) run --rm wpcli wp eval "require ABSPATH . '$(THEME)/tools/e2e-address-lookup-local.php';" --user=admin
Expand Down
31 changes: 31 additions & 0 deletions www/wp-content/themes/luziapi/tools/e2e-pilotage-perf-local.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* Test d'intégration LOCAL de performance du registre des recettes sur un
* historique volumineux (20 000 lignes). Exécution : make e2e-pilotage-perf-local.
* Copie temporaire uniquement, aucune donnée réelle touchée.
*/

declare(strict_types=1);

if (! defined('ABSPATH') || ! defined('WP_CLI')) {
return;
}

require __DIR__ . '/e2e-pilotage-perf.php';

$run = luziapi_e2e_pilotage_perf_run();

foreach ($run['results'] as $result) {
WP_CLI::log(($result['ok'] ? '✓' : '✗') . ' ' . $result['label'] . ('' !== $result['detail'] ? ' — ' . $result['detail'] : ''));
}
WP_CLI::log('Nettoyage : ' . $run['cleanup']);

if (null !== $run['fatal']) {
WP_CLI::error('Erreur fatale : ' . $run['fatal']);
}
$failed = count(array_filter($run['results'], static fn (array $r): bool => ! $r['ok']));
if ($failed > 0) {
WP_CLI::error($failed . ' assertion(s) en échec.');
}
WP_CLI::success(count($run['results']) . ' assertions OK — performance du registre validée.');
98 changes: 98 additions & 0 deletions www/wp-content/themes/luziapi/tools/e2e-pilotage-perf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/**
* Cœur partagé du test e2e de PERFORMANCE du registre des recettes sur un
* historique volumineux. Inclus par le wrapper local (WP-CLI) et le wrapper
* prod à jeton.
*
* On remplit une COPIE `CREATE TEMPORARY TABLE … LIKE` du registre avec 20 000
* lignes réparties sur l'année, puis on rejoue la vraie requête du registre
* (`WHERE occurred_at BETWEEN … ORDER BY …`) sur une fenêtre sélective. On
* vérifie : la correction à l'échelle, que le plan d'exécution utilise l'index
* `occurred_at` (donc pas de balayage complet — la requête passe à l'échelle),
* et une borne de temps généreuse (garde-fou contre une régression O(n²)).
*
* Sûreté : uniquement une table temporaire, aucune donnée réelle touchée.
*/

declare(strict_types=1);

if (! function_exists('luziapi_e2e_pilotage_perf_run')) {
/**
* @return array{results: list<array{label: string, ok: bool, detail: string}>, cleanup: string, fatal: ?string}
*/
function luziapi_e2e_pilotage_perf_run(): array
{
global $wpdb;
$results = [];
$assert = static function (string $label, bool $ok, string $detail = '') use (&$results): void {
$results[] = ['label' => $label, 'ok' => $ok, 'detail' => $detail];
};
$fatal = null;
$cleanup = 'non exécuté';
$temp = '';

$schema = new \LuziApi\Shop\Infrastructure\WordPress\PilotageSchemaManager($wpdb);

try {
$schema->migrate();
$real = $schema->tableName();
$temp = $real . '_e2e_perf_tmp';
$wpdb->query("CREATE TEMPORARY TABLE {$temp} LIKE {$real}");

// 20 000 lignes : 100 dans la fenêtre cible (juin 2099), 19 900 de bruit
// répartis sur les autres mois (jamais juin) — la fenêtre est donc très
// sélective.
$targetRows = 100;
$noiseRows = 19_900;
$noiseMonths = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12];

$values = [];
for ($i = 0; $i < $noiseRows; ++$i) {
$date = sprintf('2099-%02d-%02d 12:00:00', $noiseMonths[$i % 11], ($i % 28) + 1);
$values[] = "('{$date}', 100, 'cash', 'collection', 'noise', 1, '{$date}')";
}
for ($i = 0; $i < $targetRows; ++$i) {
$values[] = "('2099-06-15 12:00:00', 100, 'cash', 'collection', 'target', 1, '2099-06-15 12:00:00')";
}
shuffle($values);

$columns = '(occurred_at, amount_cents, payment_method, entry_type, description, created_by, created_at)';
foreach (array_chunk($values, 2_000) as $chunk) {
$wpdb->query("INSERT INTO {$temp} {$columns} VALUES " . implode(',', $chunk));
}
$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp}");
$assert('Historique volumineux inséré (20 000 lignes)', 20_000 === $total, "lignes={$total}");

$window = "occurred_at >= '2099-06-15 00:00:00' AND occurred_at <= '2099-06-15 23:59:59'";
$registerQuery = "SELECT * FROM {$temp} WHERE {$window} ORDER BY occurred_at DESC, sequence_number DESC";

// Correction à l'échelle.
$start = microtime(true);
$rows = $wpdb->get_results($registerQuery, ARRAY_A);
$elapsed = microtime(true) - $start;
$sum = array_sum(array_map(static fn (array $r): int => (int) $r['amount_cents'], $rows));
$assert('Registre à l’échelle : exactement les 100 lignes de la fenêtre', 100 === count($rows), 'lignes=' . count($rows));
$assert('Registre à l’échelle : somme correcte (100 × 1,00 € = 100 €)', 10_000 === $sum, "cents={$sum}");

// Plan d'exécution : l'index occurred_at est utilisé (pas de full scan).
$plan = $wpdb->get_results("EXPLAIN SELECT * FROM {$temp} WHERE {$window}", ARRAY_A);
$planType = strtoupper((string) ($plan[0]['type'] ?? ''));
$planKey = (string) ($plan[0]['key'] ?? '');
$assert('Plan d’exécution : pas de balayage complet (index occurred_at utilisé)', 'ALL' !== $planType && str_contains($planKey, 'occurred_at'), "type={$planType} key={$planKey}");

// Garde-fou de temps (généreux) contre une régression catastrophique.
$assert('Temps de requête raisonnable (< 1 s sur 20 000 lignes)', $elapsed < 1.0, sprintf('%.3f s', $elapsed));

$cleanup = 'ok (table temporaire uniquement, aucune donnée réelle touchée)';
} catch (\Throwable $exception) {
$fatal = $exception->getMessage();
} finally {
if ('' !== $temp) {
$wpdb->query("DROP TEMPORARY TABLE IF EXISTS {$temp}");
}
}

return ['results' => $results, 'cleanup' => $cleanup, 'fatal' => $fatal];
}
}
Loading