Files
breadcrumb-the-shire/tests/Architecture/FrontendTelemetryContractTest.php

174 lines
6.3 KiB
PHP
Raw Normal View History

2026-03-06 00:44:52 +01:00
<?php
namespace MintyPHP\Tests\Architecture;
use PHPUnit\Framework\TestCase;
class FrontendTelemetryContractTest extends TestCase
{
use ProjectFileAssertionSupport;
public function testEventTypeMappingsStayInSyncAcrossFrontendAndBackend(): void
{
$js = $this->readProjectFile('web/js/core/app-telemetry.js');
$ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');
$settings = $this->readProjectFile('lib/Service/Settings/SettingsFrontendTelemetryGateway.php');
$jsMap = $this->parseJsEventKeyToTypeMap($js);
$phpMap = $this->parsePhpEventTypeToKeyMap($ingest);
$expectedPhpMap = array_flip($jsMap);
ksort($expectedPhpMap);
ksort($phpMap);
$this->assertSame($expectedPhpMap, $phpMap);
$settingsAllowedEvents = $this->parsePhpStringArrayConstant(
$settings,
'FRONTEND_TELEMETRY_ALLOWED_EVENTS'
);
sort($settingsAllowedEvents, SORT_STRING);
$expectedAllowedEvents = array_keys($jsMap);
sort($expectedAllowedEvents, SORT_STRING);
$this->assertSame($expectedAllowedEvents, $settingsAllowedEvents);
}
public function testAllowedMetaKeysStayInSyncAcrossFrontendAndBackend(): void
{
$js = $this->readProjectFile('web/js/core/app-telemetry.js');
$ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');
$jsKeys = $this->parseJsStringSet($js, 'ALLOWED_META_KEYS');
$phpKeys = $this->parsePhpStringArrayConstant($ingest, 'ALLOWED_META_KEYS');
sort($jsKeys, SORT_STRING);
sort($phpKeys, SORT_STRING);
$this->assertSame($jsKeys, $phpKeys);
}
public function testLengthLimitsStayInSyncAcrossFrontendAndBackend(): void
{
$js = $this->readProjectFile('web/js/core/app-telemetry.js');
$ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');
$this->assertSame(
$this->parseJsIntConstant($js, 'MAX_MESSAGE_LENGTH'),
$this->parsePhpIntConstant($ingest, 'MESSAGE_MAX_LENGTH')
);
$this->assertSame(
$this->parseJsIntConstant($js, 'MAX_FINGERPRINT_LENGTH'),
$this->parsePhpIntConstant($ingest, 'FINGERPRINT_MAX_LENGTH')
);
$this->assertSame(
$this->parseJsIntConstant($js, 'MAX_META_ENTRIES'),
$this->parsePhpIntConstant($ingest, 'META_MAX_ENTRIES')
);
$this->assertSame(
$this->parseJsIntConstant($js, 'MAX_META_VALUE_LENGTH'),
$this->parsePhpIntConstant($ingest, 'META_VALUE_MAX_LENGTH')
);
}
/**
* @return array<string,string>
*/
private function parseJsEventKeyToTypeMap(string $content): array
{
if (preg_match('/const\s+EVENT_KEY_TO_TYPE\s*=\s*Object\.freeze\(\{(.*?)\}\);/s', $content, $match) !== 1) {
$this->fail('Could not parse EVENT_KEY_TO_TYPE from JS telemetry module.');
}
$map = [];
preg_match_all('/([a-z_]+)\s*:\s*\'([^\']+)\'/', $match[1], $pairs, PREG_SET_ORDER);
foreach ($pairs as $pair) {
$map[$pair[1]] = $pair[2];
}
$this->assertNotSame([], $map, 'EVENT_KEY_TO_TYPE map is empty.');
return $map;
}
/**
* @return array<string,string>
*/
private function parsePhpEventTypeToKeyMap(string $content): array
{
$constValues = [];
preg_match_all('/private const\s+([A-Z0-9_]+)\s*=\s*\'([^\']+)\';/', $content, $constants, PREG_SET_ORDER);
foreach ($constants as $constant) {
$constValues[$constant[1]] = $constant[2];
}
if (preg_match('/private const EVENT_KEY_MAP = \[(.*?)\];/s', $content, $match) !== 1) {
$this->fail('Could not parse EVENT_KEY_MAP from ingest service.');
}
$map = [];
preg_match_all('/self::([A-Z0-9_]+)\s*=>\s*\'([a-z_]+)\'/', $match[1], $pairs, PREG_SET_ORDER);
foreach ($pairs as $pair) {
$eventConstant = $pair[1];
$eventType = $constValues[$eventConstant] ?? '';
$this->assertNotSame('', $eventType, 'Missing event constant for EVENT_KEY_MAP entry: ' . $eventConstant);
$map[$eventType] = $pair[2];
}
$this->assertNotSame([], $map, 'EVENT_KEY_MAP is empty.');
return $map;
}
/**
* @return list<string>
*/
private function parseJsStringSet(string $content, string $constantName): array
{
$pattern = '/const\s+' . preg_quote($constantName, '/') . '\s*=\s*new Set\(\[(.*?)\]\);/s';
if (preg_match($pattern, $content, $match) !== 1) {
$this->fail('Could not parse JS Set constant: ' . $constantName);
}
$values = [];
preg_match_all('/\'([^\']+)\'/', $match[1], $items, PREG_SET_ORDER);
foreach ($items as $item) {
$values[] = $item[1];
}
return array_values(array_unique($values));
}
/**
* @return list<string>
*/
private function parsePhpStringArrayConstant(string $content, string $constantName): array
{
$pattern = '/private const\s+' . preg_quote($constantName, '/') . '\s*=\s*\[(.*?)\];/s';
if (preg_match($pattern, $content, $match) !== 1) {
$this->fail('Could not parse PHP array constant: ' . $constantName);
}
$values = [];
preg_match_all('/\'([^\']+)\'/', $match[1], $items, PREG_SET_ORDER);
foreach ($items as $item) {
$values[] = $item[1];
}
return array_values(array_unique($values));
}
private function parseJsIntConstant(string $content, string $constantName): int
{
$pattern = '/const\s+' . preg_quote($constantName, '/') . '\s*=\s*([0-9]+)\s*;/';
if (preg_match($pattern, $content, $match) !== 1) {
$this->fail('Could not parse JS integer constant: ' . $constantName);
}
return (int) $match[1];
}
private function parsePhpIntConstant(string $content, string $constantName): int
{
$pattern = '/private const\s+' . preg_quote($constantName, '/') . '\s*=\s*([0-9]+)\s*;/';
if (preg_match($pattern, $content, $match) !== 1) {
$this->fail('Could not parse PHP integer constant: ' . $constantName);
}
return (int) $match[1];
}
}