From 0b8a890efb6c1c45b1a55eabc95c3792c338e892 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 1 Jul 2026 11:25:06 +0200 Subject: [PATCH 1/4] fix: make feature flag local tests deterministic --- .changeset/calm-flags-compare.md | 5 ++ test/FeatureFlagLocalEvaluationTest.php | 92 ++++++++++++++++++++----- 2 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 .changeset/calm-flags-compare.md diff --git a/.changeset/calm-flags-compare.md b/.changeset/calm-flags-compare.md new file mode 100644 index 0000000..47b917e --- /dev/null +++ b/.changeset/calm-flags-compare.md @@ -0,0 +1,5 @@ +--- +'posthog-php': patch +--- + +Make feature flag local evaluation tests deterministic. diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 479bc81..e735eec 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -27,33 +27,92 @@ public function setUp(): void { date_default_timezone_set("UTC"); + // This class verifies legacy single-flag local evaluation behavior. Deprecation + // messages for those methods are asserted in FeatureFlagEvaluationsTest. + $previous = null; + $previous = set_error_handler( + function (int $errno, string $errstr) use (&$previous) { + if ($errno === E_USER_DEPRECATED && $this->isLegacyFeatureFlagDeprecation($errstr)) { + return true; + } + + if ($previous !== null) { + return $previous($errno, $errstr); + } + + return false; + }, + E_USER_DEPRECATED + ); + // Reset the errorMessages array before each test global $errorMessages; $errorMessages = []; } + public function tearDown(): void + { + restore_error_handler(); + } + + private function isLegacyFeatureFlagDeprecation(string $message): bool + { + return str_contains($message, 'Client::getFeatureFlag() is deprecated') + || str_contains($message, 'Client::isFeatureEnabled() is deprecated') + || str_contains($message, 'Client::getFeatureFlagPayload() is deprecated'); + } + public function checkEmptyErrorLogs(): void { global $errorMessages; $this->assertTrue(empty($errorMessages), "Error logs are not empty: " . implode("\n", $errorMessages)); } - private function assertAndStripBatchUuid(int $callIndex): void + private function assertHttpCallsEqual(array $expectedCalls): void { - $payload = json_decode($this->http_client->calls[$callIndex]['payload'], true); - $this->assertIsArray($payload); - $this->assertArrayHasKey('batch', $payload); - - foreach ($payload['batch'] as $index => $event) { - $this->assertArrayHasKey('uuid', $event); - $this->assertMatchesRegularExpression( - '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', - $event['uuid'] - ); - unset($payload['batch'][$index]['uuid']); + $this->assertEquals( + $this->normalizeHttpCalls($expectedCalls, false), + $this->normalizeHttpCalls($this->http_client->calls ?? [], true) + ); + } + + private function normalizeHttpCalls(array $calls, bool $assertBatchUuids): array + { + foreach ($calls as $callIndex => $call) { + if (!isset($call['payload']) || !is_string($call['payload'])) { + continue; + } + + $payload = json_decode($call['payload'], true); + if (json_last_error() !== JSON_ERROR_NONE) { + continue; + } + + if (isset($payload['batch']) && is_array($payload['batch'])) { + foreach ($payload['batch'] as $eventIndex => $event) { + if (!is_array($event)) { + continue; + } + + if (!array_key_exists('uuid', $event)) { + if ($assertBatchUuids) { + $this->fail('Expected batch event to include a UUID'); + } + continue; + } + + $this->assertMatchesRegularExpression( + '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', + $event['uuid'] + ); + unset($payload['batch'][$eventIndex]['uuid']); + } + } + + $calls[$callIndex]['payload'] = $payload; } - $this->http_client->calls[$callIndex]['payload'] = json_encode($payload); + return $calls; } public function testMatchPropertyEquals(): void @@ -1460,9 +1519,7 @@ public function testSimpleFlag() PostHog::flush(); - $this->assertAndStripBatchUuid(1); - $this->assertEquals( - $this->http_client->calls, + $this->assertHttpCallsEqual( array( 0 => array( "path" => "/flags/definitions?send_cohorts&token=random_key", @@ -1708,8 +1765,7 @@ public function testFeatureFlagsLocalEvaluationForNegatedCohorts() ); # since 'other' is negated, we return False. Since 'nation' is not present, we can't tell whether the flag should be true or false, so go to decide $this->assertEquals($feature_flag_match, 'decide-fallback-value'); - $this->assertEquals( - $this->http_client->calls, + $this->assertHttpCallsEqual( array( 0 => array( "path" => "/flags/?v=2", From 32e2e1ab208a0ba1ed92fe98e363f0972bcf0877 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 1 Jul 2026 11:26:17 +0200 Subject: [PATCH 2/4] chore: remove unnecessary changeset --- .changeset/calm-flags-compare.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/calm-flags-compare.md diff --git a/.changeset/calm-flags-compare.md b/.changeset/calm-flags-compare.md deleted file mode 100644 index 47b917e..0000000 --- a/.changeset/calm-flags-compare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'posthog-php': patch ---- - -Make feature flag local evaluation tests deterministic. From 10a7d7b1e0a8cd8d01ba6b4e8c08c31656cdb3eb Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 1 Jul 2026 11:30:33 +0200 Subject: [PATCH 3/4] ci: suppress inherited secrets semgrep warning --- .github/workflows/call-flags-project-board.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/call-flags-project-board.yml b/.github/workflows/call-flags-project-board.yml index 0e74e28..2904aeb 100644 --- a/.github/workflows/call-flags-project-board.yml +++ b/.github/workflows/call-flags-project-board.yml @@ -14,4 +14,4 @@ jobs: pr_number: ${{ github.event.pull_request.number }} pr_node_id: ${{ github.event.pull_request.node_id }} is_draft: ${{ github.event.pull_request.draft }} - secrets: inherit \ No newline at end of file + secrets: inherit # nosemgrep: yaml.github-actions.security.secrets-inherit.secrets-inherit -- reusable workflow requires inherited secrets \ No newline at end of file From 907b33d447fbce9947a5ceab0441f32830197f94 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 1 Jul 2026 11:33:41 +0200 Subject: [PATCH 4/4] chore: address feature flag test review --- test/FeatureFlagLocalEvaluationTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index e735eec..f5b5150 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -29,7 +29,6 @@ public function setUp(): void // This class verifies legacy single-flag local evaluation behavior. Deprecation // messages for those methods are asserted in FeatureFlagEvaluationsTest. - $previous = null; $previous = set_error_handler( function (int $errno, string $errstr) use (&$previous) { if ($errno === E_USER_DEPRECATED && $this->isLegacyFeatureFlagDeprecation($errstr)) {