feat: replace chb with consent groups and preserve runtime config (api#317, #17)
deploy-preland / deploy zoomstage.online (push) Successful in 42s
deploy-preland / deploy zoomstage.online (push) Successful in 42s
This commit is contained in:
1 parent
188b78a115
commit
89cc16eb4a
12 files changed
+208
-9
No files matched your search
@@ -129,6 +129,32 @@ jobs:
|
|||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||||
echo "залито файлов: $(wc -l < "$files")"
|
echo "залито файлов: $(wc -l < "$files")"
|
||||||
|
|
||||||
|
# Сначала совместимый PHP-код: он понимает старый host-config без chb.
|
||||||
|
# Затем меняем только согласованные consent-ключи, не заменяя весь config снимком Git.
|
||||||
|
- name: Migrate consent UTM in preserved host config
|
||||||
|
env:
|
||||||
|
SFTP_HOST: ${{ secrets.ZOOMPIXEL_SFTP_HOST }}
|
||||||
|
SFTP_USER: ${{ secrets.ZOOMPIXEL_SFTP_USER }}
|
||||||
|
SFTP_PASS: ${{ secrets.ZOOMPIXEL_SFTP_PASS }}
|
||||||
|
SFTP_KNOWN_HOSTS: ${{ secrets.ZOOMPIXEL_SFTP_KNOWN_HOSTS }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
umask 077
|
||||||
|
known_hosts=$(mktemp); batch=$(mktemp); config=$(mktemp)
|
||||||
|
trap 'rm -f "$known_hosts" "$batch" "$config"' EXIT
|
||||||
|
printf '%s\n' "$SFTP_KNOWN_HOSTS" > "$known_hosts"
|
||||||
|
printf '%s\n' "get -p $REMOTE_DIR/config.json $config" > "$batch"
|
||||||
|
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||||
|
changed=0
|
||||||
|
php bin/ensure-cons-config.php "$config" || changed=$?
|
||||||
|
case "$changed" in
|
||||||
|
0) ;;
|
||||||
|
10) revision=$(git rev-parse HEAD)
|
||||||
|
printf '%s\n' "put -p $config $REMOTE_DIR/config.json.upload-$revision" "rename $REMOTE_DIR/config.json.upload-$revision $REMOTE_DIR/config.json" > "$batch"
|
||||||
|
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST" ;;
|
||||||
|
*) exit "$changed" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
- name: Check the page
|
- name: Check the page
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Preland всегда формирует полный resolved-набор сво
|
|||||||
| `degr` | пресет ухудшения превью «До» | сайт (#42) |
|
| `degr` | пресет ухудшения превью «До» | сайт (#42) |
|
||||||
| `registration` | `before` — PIN до оплаты, `after` — после | сайт, страница результата |
|
| `registration` | `before` — PIN до оплаты, `after` — после | сайт, страница результата |
|
||||||
| `bchoice` | попап выбора банка (`a` — показать) | сайт, оплата (#44) |
|
| `bchoice` | попап выбора банка (`a` — показать) | сайт, оплата (#44) |
|
||||||
| `chb` | галочки согласия на странице оплаты (`no` — не обязательны) | сайт, оплата |
|
| `cons` | номер группы согласий на Join; `rnd` выбирает группу по весам, исходно 1 с вероятностью 100%, default — 2 | сайт, оплата |
|
||||||
| `tp` | группа тарифов | сайт, cpanel «Группы тарифов (tp)» |
|
| `tp` | группа тарифов | сайт, cpanel «Группы тарифов (tp)» |
|
||||||
| `focus` | первоначальная область лупы Join: `center`/`face` | сайт, cpanel после `tp` |
|
| `focus` | первоначальная область лупы Join: `center`/`face` | сайт, cpanel после `tp` |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/** preland#17: миграция только utm.chb → utm.cons; остальные значения владельца сохраняются. */
|
||||||
|
declare(strict_types=1);
|
||||||
|
if ($argc !== 2) {
|
||||||
|
fwrite(STDERR, "Использование: php bin/ensure-cons-config.php /path/to/config.json\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$path = $argv[1];
|
||||||
|
$raw = @file_get_contents($path);
|
||||||
|
if ($raw === false) throw new RuntimeException('Не удалось прочитать config.json.');
|
||||||
|
// Object decode сохраняет типы соседних {} и объектов с числовыми ключами.
|
||||||
|
$config = json_decode($raw, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
if (!$config instanceof stdClass || !($config->utm ?? null) instanceof stdClass) throw new RuntimeException('В config.json отсутствует объект utm.');
|
||||||
|
$hasCons = property_exists($config->utm, 'cons');
|
||||||
|
$cons = $hasCons ? $config->utm->cons : (object) ['weights' => (object) ['1' => 100, '2' => 0], 'default' => '2'];
|
||||||
|
if (!$cons instanceof stdClass || !(is_array($cons->weights ?? null) || ($cons->weights ?? null) instanceof stdClass)
|
||||||
|
|| !(is_string($cons->default ?? null) || is_int($cons->default ?? null))
|
||||||
|
|| !preg_match('/^[1-9][0-9]*$/D', (string) $cons->default)
|
||||||
|
|| (float) $cons->default > 2147483647) {
|
||||||
|
throw new RuntimeException('Существующий utm.cons некорректен; автоматическая перезапись запрещена.');
|
||||||
|
}
|
||||||
|
if (!property_exists($config->utm, 'chb') && $hasCons) {
|
||||||
|
fwrite(STDOUT, "cons уже настроен: config не изменён.\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
unset($config->utm->chb);
|
||||||
|
$config->utm->cons = $cons;
|
||||||
|
$encoded = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
|
||||||
|
if (file_put_contents($path, $encoded, LOCK_EX) === false) throw new RuntimeException('Не удалось записать config.json.');
|
||||||
|
fwrite(STDOUT, "Мигрированы только utm.chb/cons; остальные поля и существующие настройки cons сохранены.\n");
|
||||||
|
exit(10);
|
||||||
|
} catch (Throwable $error) {
|
||||||
|
fwrite(STDERR, $error->getMessage() . "\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/** Собственный контракт cons Preland; существование группы проверяет основной API. */
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/** Переходный runtime-конфиг: новый код работает и до точечного обновления host config. */
|
||||||
|
function prelandNormalizeConsConfig(array $config): array
|
||||||
|
{
|
||||||
|
$config['utm'] = is_array($config['utm'] ?? null) ? $config['utm'] : [];
|
||||||
|
unset($config['utm']['chb']);
|
||||||
|
if (!is_array($config['utm']['cons'] ?? null)) {
|
||||||
|
$config['utm']['cons'] = ['weights' => ['1' => 100, '2' => 0], 'default' => '2'];
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Concrete номера не ограничены random-весами: новые группы создаются в CPanel API. */
|
||||||
|
function prelandResolveCons(mixed $value, array $settings): string
|
||||||
|
{
|
||||||
|
$valid = static fn (mixed $candidate): bool => (is_string($candidate) || is_int($candidate))
|
||||||
|
&& preg_match('/^[1-9][0-9]{0,9}$/D', (string) $candidate) === 1
|
||||||
|
&& (float) $candidate <= 2147483647;
|
||||||
|
$default = $valid($settings['default'] ?? null) ? (string) $settings['default'] : '2';
|
||||||
|
if ($valid($value)) return (string) $value;
|
||||||
|
if ($value !== 'rnd') return $default;
|
||||||
|
|
||||||
|
$weights = [];
|
||||||
|
foreach ((array) ($settings['weights'] ?? []) as $number => $weight) {
|
||||||
|
if ($valid($number) && is_numeric($weight) && is_finite((float) $weight) && (float) $weight > 0) {
|
||||||
|
$weights[(string) $number] = (float) $weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$total = array_sum($weights);
|
||||||
|
if ($total <= 0 || !is_finite($total)) return $default;
|
||||||
|
$point = (mt_rand() / (mt_getrandmax() + 1.0)) * $total;
|
||||||
|
foreach ($weights as $number => $weight) {
|
||||||
|
if ($point < $weight) return (string) $number;
|
||||||
|
$point -= $weight;
|
||||||
|
}
|
||||||
|
return (string) array_key_last($weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Legacy chb не должен проходить как неизвестный внешний рекламный параметр. */
|
||||||
|
function prelandNormalizeConsQuery(array $query, array $settings): array
|
||||||
|
{
|
||||||
|
unset($query['chb']);
|
||||||
|
$query['cons'] = prelandResolveCons($query['cons'] ?? null, $settings);
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
+12
-1
@@ -72,6 +72,16 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
|
|||||||
const utmData = <?= $utmJson ?>;
|
const utmData = <?= $utmJson ?>;
|
||||||
const redirectUrlBase = <?= json_encode($redirectUrlBase, JSON_UNESCAPED_UNICODE) ?>;
|
const redirectUrlBase = <?= json_encode($redirectUrlBase, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
const redirectUrlUtm = <?= json_encode($redirectUrlUtm, JSON_UNESCAPED_UNICODE) ?>;
|
const redirectUrlUtm = <?= json_encode($redirectUrlUtm, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
// Сохранённая до перехода на cons загрузка не должна вернуть legacy chb.
|
||||||
|
const normalizeConsentRedirect = query => {
|
||||||
|
const params = new URLSearchParams(query || '');
|
||||||
|
params.delete('chb');
|
||||||
|
const value = params.get('cons');
|
||||||
|
if (!/^[1-9][0-9]{0,9}$/.test(value || '') || Number(value) > 2147483647) {
|
||||||
|
params.set('cons', String(utmData.cons || '2'));
|
||||||
|
}
|
||||||
|
return params.toString();
|
||||||
|
};
|
||||||
const createApiUrl = <?= json_encode($createApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
const createApiUrl = <?= json_encode($createApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
const uploadPolicyApiUrl = <?= json_encode($uploadPolicyApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
const uploadPolicyApiUrl = <?= json_encode($uploadPolicyApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
|
||||||
@@ -202,7 +212,8 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
|
|||||||
|
|
||||||
function redirectToTour(record) {
|
function redirectToTour(record) {
|
||||||
let targetUrl = record.meta.redirectBase + encodeURIComponent(record.key);
|
let targetUrl = record.meta.redirectBase + encodeURIComponent(record.key);
|
||||||
if (record.meta.redirectUtm) targetUrl += '&' + record.meta.redirectUtm;
|
const redirectQuery = normalizeConsentRedirect(record.meta.redirectUtm);
|
||||||
|
if (redirectQuery) targetUrl += '&' + redirectQuery;
|
||||||
const shownTheme = window.ZoomzillaTheme.current();
|
const shownTheme = window.ZoomzillaTheme.current();
|
||||||
targetUrl += '&shown_theme=' + encodeURIComponent(shownTheme);
|
targetUrl += '&shown_theme=' + encodeURIComponent(shownTheme);
|
||||||
window.PublicNavigationGuard.allowNavigation(() => { window.location.href = targetUrl; });
|
window.PublicNavigationGuard.allowNavigation(() => { window.location.href = targetUrl; });
|
||||||
|
|||||||
+4
-4
@@ -39,12 +39,12 @@
|
|||||||
},
|
},
|
||||||
"default": "a"
|
"default": "a"
|
||||||
},
|
},
|
||||||
"chb": {
|
"cons": {
|
||||||
"weights": {
|
"weights": {
|
||||||
"a": 1,
|
"1": 100,
|
||||||
"b": 0
|
"2": 0
|
||||||
},
|
},
|
||||||
"default": "b"
|
"default": "2"
|
||||||
},
|
},
|
||||||
"tp": {
|
"tp": {
|
||||||
"weights": {
|
"weights": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ require_once __DIR__ . '/i18n.php';
|
|||||||
require_once __DIR__ . '/components/asset-url.php';
|
require_once __DIR__ . '/components/asset-url.php';
|
||||||
require_once __DIR__ . '/components/site-domain.php';
|
require_once __DIR__ . '/components/site-domain.php';
|
||||||
require_once __DIR__ . '/components/theme-preference.php';
|
require_once __DIR__ . '/components/theme-preference.php';
|
||||||
|
require_once __DIR__ . '/components/cons-utm.php';
|
||||||
|
|
||||||
$configFile = __DIR__ . '/config.json';
|
$configFile = __DIR__ . '/config.json';
|
||||||
$config = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : [];
|
$config = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : [];
|
||||||
@@ -13,6 +14,7 @@ if (json_last_error() !== JSON_ERROR_NONE) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Yii::init($config['default_language'] ?? 'ru');
|
Yii::init($config['default_language'] ?? 'ru');
|
||||||
|
$config = prelandNormalizeConsConfig($config);
|
||||||
|
|
||||||
function getWeightedRandom($weights)
|
function getWeightedRandom($weights)
|
||||||
{
|
{
|
||||||
@@ -40,10 +42,11 @@ function getWeightedRandom($weights)
|
|||||||
return (string)end($keys);
|
return (string)end($keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
$get_params = $_GET;
|
$get_params = prelandNormalizeConsQuery($_GET, $config['utm']['cons']);
|
||||||
|
|
||||||
if (isset($config['utm']) && is_array($config['utm'])) {
|
if (isset($config['utm']) && is_array($config['utm'])) {
|
||||||
foreach ($get_params as $key => $value) {
|
foreach ($get_params as $key => $value) {
|
||||||
|
if ($key === 'cons') continue;
|
||||||
if (!isset($config['utm'][$key])) {
|
if (!isset($config['utm'][$key])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
/** preland#17: точечный, идемпотентный patch только chb/cons и его defaults/weights. */
|
||||||
|
declare(strict_types=1);
|
||||||
|
$script=dirname(__DIR__).'/bin/ensure-cons-config.php';
|
||||||
|
if(!is_file($script)){fwrite(STDERR,"FAIL: patch cons ещё не реализован.\n");exit(1);}
|
||||||
|
$fixture=['host'=>'keep.example','requisites'=>['Не менять'],'utm'=>['theme'=>['default'=>'dark','weights'=>['light'=>7]],'chb'=>['default'=>'b','weights'=>['a'=>1,'b'=>0]],'focus'=>['default'=>'face']]];
|
||||||
|
$path=tempnam(sys_get_temp_dir(),'cons-config-');
|
||||||
|
try{
|
||||||
|
file_put_contents($path,json_encode($fixture,JSON_THROW_ON_ERROR));
|
||||||
|
exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($script).' '.escapeshellarg($path),$output,$code);
|
||||||
|
$actual=json_decode(file_get_contents($path),true,512,JSON_THROW_ON_ERROR);
|
||||||
|
if($code!==10||isset($actual['utm']['chb'])||($actual['utm']['cons']??null)!==['weights'=>['1'=>100,'2'=>0],'default'=>'2'])throw new RuntimeException('Patch должен заменить только согласованный ключ chb на cons.');
|
||||||
|
$comparison=$actual;unset($comparison['utm']['cons']);$old=$fixture;unset($old['utm']['chb']);
|
||||||
|
if($comparison!==$old)throw new RuntimeException('Patch изменил соседние поля runtime config.');
|
||||||
|
$before=file_get_contents($path);exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($script).' '.escapeshellarg($path),$output,$code);
|
||||||
|
if($code!==0||file_get_contents($path)!==$before)throw new RuntimeException('Повторный patch должен оставить байты без изменений.');
|
||||||
|
$custom=$actual;$custom['utm']['cons']=['weights'=>['1'=>20,'3'=>80],'default'=>'3'];
|
||||||
|
file_put_contents($path,json_encode($custom,JSON_THROW_ON_ERROR));$before=file_get_contents($path);
|
||||||
|
exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($script).' '.escapeshellarg($path),$output,$code);
|
||||||
|
if($code!==0||file_get_contents($path)!==$before)throw new RuntimeException('Следующий deploy не должен сбросить настроенный владельцем cons.');
|
||||||
|
foreach([[],true,1.5,'2147483648','0']as$invalid){
|
||||||
|
$broken=$custom;$broken['utm']['cons']['default']=$invalid;
|
||||||
|
file_put_contents($path,json_encode($broken,JSON_THROW_ON_ERROR));$before=file_get_contents($path);
|
||||||
|
exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($script).' '.escapeshellarg($path).' 2>/dev/null',$output,$code);
|
||||||
|
if($code!==2||file_get_contents($path)!==$before)throw new RuntimeException('Некорректный существующий cons должен остановить deploy без перезаписи config.');
|
||||||
|
}
|
||||||
|
$objectFixture='{"host":"keep","extra":{},"map":{"0":"a","1":"b"},"list":[],"utm":{"chb":{"default":"a"},"other":{"weights":{}}}}';
|
||||||
|
file_put_contents($path,$objectFixture);exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($script).' '.escapeshellarg($path),$output,$code);
|
||||||
|
$beforeObject=json_decode($objectFixture,false,512,JSON_THROW_ON_ERROR);$afterObject=json_decode(file_get_contents($path),false,512,JSON_THROW_ON_ERROR);
|
||||||
|
unset($beforeObject->utm->chb,$afterObject->utm->cons);
|
||||||
|
if($code!==10||json_encode($beforeObject)!==json_encode($afterObject))throw new RuntimeException('Patch должен сохранять типы соседних пустых и numeric-key JSON объектов.');
|
||||||
|
echo "PASS: cons config migration preserves other fields and JSON types, idempotent\n";
|
||||||
|
}finally{@unlink($path);}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
/** preland#17: consent-конфиг мигрируется после совместимого кода, без замены runtime-снимком. */
|
||||||
|
declare(strict_types=1);
|
||||||
|
$root=dirname(__DIR__);$workflow=file_get_contents($root.'/.gitea/workflows/deploy.yml');
|
||||||
|
$upload=strpos($workflow,'- name: Upload over SFTP');$patch=strpos($workflow,'- name: Migrate consent UTM in preserved host config');
|
||||||
|
if($upload===false||$patch===false||$patch<$upload)throw new RuntimeException('Consent config patch должен выполняться после совместимого кода.');
|
||||||
|
$section=substr($workflow,$patch,strpos($workflow,'- name: Check the page',$patch)-$patch);
|
||||||
|
foreach(['get -p $REMOTE_DIR/config.json','ensure-cons-config.php','config.json.upload-$revision','rename $REMOTE_DIR/config.json.upload-$revision']as$expected){if(!str_contains($section,$expected))throw new RuntimeException('Отсутствует безопасный этап: '.$expected);}
|
||||||
|
if(str_contains($section,'put -p config.json '))throw new RuntimeException('Снимок Git не должен заменять runtime config.');
|
||||||
|
echo "PASS: consent config patch follows compatible code and uses atomic targeted replacement\n";
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/** preland#17: concrete cons независим от random-весов, старый chb не управляет переходом. */
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$file = dirname(__DIR__) . '/components/cons-utm.php';
|
||||||
|
if (!is_file($file)) {
|
||||||
|
fwrite(STDERR, "FAIL: нормализация cons ещё не реализована.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
require $file;
|
||||||
|
$checks = 0;
|
||||||
|
$expect = static function (bool $ok, string $message) use (&$checks): void {
|
||||||
|
if (!$ok) throw new RuntimeException($message);
|
||||||
|
$checks++;
|
||||||
|
};
|
||||||
|
$fixture = ['host'=>'api.example.test','utm'=>['theme'=>['default'=>'dark','weights'=>['dark'=>3]],'chb'=>['default'=>'a','weights'=>['a'=>1]]]];
|
||||||
|
$normalized = prelandNormalizeConsConfig($fixture);
|
||||||
|
$expect(!isset($normalized['utm']['chb']), 'Старый chb должен быть удалён из runtime-копии конфига.');
|
||||||
|
$expect($normalized['host'] === $fixture['host'] && $normalized['utm']['theme'] === $fixture['utm']['theme'], 'Соседние поля должны сохраняться.');
|
||||||
|
$expect($fixture['utm']['chb']['default'] === 'a', 'Нормализация не должна менять исходный массив.');
|
||||||
|
$settings = $normalized['utm']['cons'];
|
||||||
|
$expect(prelandResolveCons(null, $settings) === '2', 'Отсутствующее значение должно получить default2.');
|
||||||
|
foreach (['1','2','3','99'] as $value) $expect(prelandResolveCons($value, $settings) === $value, 'Concrete cons должен приниматься независимо от весов: '.$value);
|
||||||
|
foreach (['0','-1','a','1x','',[],['1']] as $value) $expect(prelandResolveCons($value, $settings) === '2', 'Неверное значение должно получить default2.');
|
||||||
|
for ($i=0; $i<1000; $i++) $expect(prelandResolveCons('rnd', $settings) === '1', 'rnd обязан выбрать только группу1.');
|
||||||
|
$settings = ['default'=>'2','weights'=>['2'=>0,'1'=>100]];
|
||||||
|
for ($i=0; $i<1000; $i++) $expect(prelandResolveCons('rnd', $settings) === '1', 'Нулевой вес не должен случайно попасть в выборку.');
|
||||||
|
$query = prelandNormalizeConsQuery(['chb'=>'a','cons'=>'3','utm_source'=>'ad','subid'=>'42'], $settings);
|
||||||
|
$expect($query === ['cons'=>'3','utm_source'=>'ad','subid'=>'42'], 'Query должен удалить только chb и нормализовать cons.');
|
||||||
|
$query = prelandNormalizeConsQuery(['chb'=>['b'],'utm_source'=>'ad'], $settings);
|
||||||
|
$expect(!isset($query['chb']) && $query['cons'] === '2' && $query['utm_source'] === 'ad', 'Array legacy chb не должен влиять на fallback.');
|
||||||
|
echo "PASS: cons resolver {$checks} checks\n";
|
||||||
@@ -37,7 +37,7 @@ $expected = [
|
|||||||
'degr' => '1',
|
'degr' => '1',
|
||||||
'registration' => 'after',
|
'registration' => 'after',
|
||||||
'bchoice' => 'a',
|
'bchoice' => 'a',
|
||||||
'chb' => 'b',
|
'cons' => '2',
|
||||||
'tp' => $config['utm']['tp']['default'] ?? null,
|
'tp' => $config['utm']['tp']['default'] ?? null,
|
||||||
'focus' => 'face',
|
'focus' => 'face',
|
||||||
'utm_source' => 'stage-ad',
|
'utm_source' => 'stage-ad',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ if (!is_array($config)) {
|
|||||||
$failures[] = 'Рабочий config.json должен фиксировать явно согласованный focus=face.';
|
$failures[] = 'Рабочий config.json должен фиксировать явно согласованный focus=face.';
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (['indpic', 'theme', 'degr', 'registration', 'bchoice', 'chb', 'tp'] as $key) {
|
foreach (['indpic', 'theme', 'degr', 'registration', 'bchoice', 'cons', 'tp'] as $key) {
|
||||||
$setting = $config['utm'][$key] ?? null;
|
$setting = $config['utm'][$key] ?? null;
|
||||||
if (!is_array($setting) || !is_array($setting['weights'] ?? null) || !is_string($setting['default'] ?? null)) {
|
if (!is_array($setting) || !is_array($setting['weights'] ?? null) || !is_string($setting['default'] ?? null)) {
|
||||||
$failures[] = "Рабочий config.json должен хранить валидный stage-снимок UTM {$key}.";
|
$failures[] = "Рабочий config.json должен хранить валидный stage-снимок UTM {$key}.";
|
||||||
|
|||||||
Reference in new issue
Block a user