forked from fa/breadcrumb-the-shire
174 lines
6.3 KiB
PHP
174 lines
6.3 KiB
PHP
<?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];
|
|
}
|
|
}
|