Skip to content
Open
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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "tests/Engine/EngineTests/EngineTestData"]
path = tests/Engine/EngineTests/EngineTestData
url = git@github.com:Flagsmith/engine-test-data.git
tag = v3.4.1
branch = feat/mappers/add-mapper-test-data
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to use tagged version.

4 changes: 2 additions & 2 deletions src/Utils/Mappers.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private static function _mapIdentityOverridesToSegments($identityOverrides)
];
$overridesKey[] = $part;
}
$featuresToIdentifiers[serialize($overridesKey)][] = $identityOverride->identifier;
$featuresToIdentifiers[json_encode($overridesKey)][] = $identityOverride->identifier;
}

/** @var array<string, SegmentContext> */
Expand All @@ -187,7 +187,7 @@ private static function _mapIdentityOverridesToSegments($identityOverrides)
$segment->rules = [$identifiersRule];

$segment->overrides = [];
foreach (unserialize($serializedOverridesKey) as $overrideKey) {
foreach (json_decode($serializedOverridesKey, true) as $overrideKey) {
[$featureName, $enabled, $value] = $overrideKey;
$featureId = $featureIDsByName[$featureName];
$feature = new FeatureContext();
Expand Down
142 changes: 33 additions & 109 deletions tests/Utils/MappersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,125 +2,49 @@

namespace FlagsmithTest\Utils;

use FlagsmithTest\ClientFixtures;
use Flagsmith\Engine\Utils\Types\Context\EnvironmentContext;
use Flagsmith\Engine\Utils\Types\Context\EvaluationContext;
use Flagsmith\Engine\Utils\Types\Context\SegmentRuleType;
use Flagsmith\Engine\Utils\Types\Context\SegmentConditionOperator;
use Flagsmith\Utils\Mappers;
use PHPUnit\Framework\TestCase;

class MappersTest extends TestCase
{
public function testMapEnvironmentDocumentToContextProducesEvaluationContext(): void
/** @return \Generator<string, array<array<string, mixed>>> */
public function extractMapperTestCases(): \Generator
{
$testCasePaths = glob(__DIR__ . '/../Engine/EngineTests/EngineTestData/mapper_test_cases/test_*.{json,jsonc}', \GLOB_BRACE);
foreach ($testCasePaths as $testCasePath) {
$testCaseJson = file_get_contents($testCasePath);
$testCase = json5_decode($testCaseJson);

$testName = basename($testCasePath);
yield $testName => [[
'environment_document' => $testCase->environment_document,
'expected_evaluation_context' => $testCase->expected_evaluation_context,
]];
}
}

/**
* @dataProvider extractMapperTestCases
* @param array<string, mixed> $case
* @return void
*/
public function testMapEnvironmentDocumentToContextMatchesTestData($case): void
{
// Given
$environment = ClientFixtures::getEnvironmentModel();
$environmentDocument = $case['environment_document'];

// When
$context = Mappers::mapEnvironmentDocumentToContext($environment);

// Then
$this->assertInstanceOf(EvaluationContext::class, $context);
$this->assertEquals('B62qaMZNwfiqT76p38ggrQ', $context->environment->key);
$this->assertEquals('Test environment', $context->environment->name);
$this->assertNull($context->identity);
$this->assertCount(2, $context->segments);

$this->assertArrayHasKey(0, $context->segments);
$this->assertEquals(1, $context->segments[0]->key);
$this->assertEquals('Test segment', $context->segments[0]->name);
$this->assertCount(1, $context->segments[0]->rules);
$this->assertEmpty($context->segments[0]->overrides);
$this->assertEquals('api', $context->segments[0]->metadata['source']);
$this->assertEquals('1', $context->segments[0]->metadata['id']);

$this->assertEquals(SegmentRuleType::ALL, $context->segments[0]->rules[0]->type);
$this->assertEmpty($context->segments[0]->rules[0]->conditions);
$this->assertCount(1, $context->segments[0]->rules[0]->rules);

$this->assertEquals(SegmentRuleType::ALL, $context->segments[0]->rules[0]->rules[0]->type);
$this->assertCount(1, $context->segments[0]->rules[0]->rules[0]->conditions);
$this->assertEmpty($context->segments[0]->rules[0]->rules[0]->rules);

$this->assertEquals('foo', $context->segments[0]->rules[0]->rules[0]->conditions[0]->property);
$this->assertEquals(SegmentConditionOperator::EQUAL, $context->segments[0]->rules[0]->rules[0]->conditions[0]->operator);
$this->assertEquals('bar', $context->segments[0]->rules[0]->rules[0]->conditions[0]->value);
$actual = Mappers::mapEnvironmentDocumentToContext($environmentDocument);

$overrideKey = '2a3691c8a306223592e2e657e50c44cf126db84730e813adea6f951c502b19e8';
$this->assertArrayHasKey($overrideKey, $context->segments);
$this->assertEquals('', $context->segments[$overrideKey]->key);
$this->assertEquals('identity_overrides', $context->segments[$overrideKey]->name);
$this->assertCount(1, $context->segments[$overrideKey]->rules);
$this->assertCount(1, $context->segments[$overrideKey]->overrides);
// Replace -INF with string "-INF" to allow JSON encoding
$serialized = serialize($actual);
$serialized = str_replace('d:-INF;', 's:4:"-INF";', $serialized);
$actual = unserialize($serialized);

$this->assertEquals(SegmentRuleType::ALL, $context->segments[$overrideKey]->rules[0]->type);
$this->assertCount(1, $context->segments[$overrideKey]->rules[0]->conditions);
$this->assertNull($context->segments[$overrideKey]->rules[0]->rules);

$this->assertEquals('$.identity.identifier', $context->segments[$overrideKey]->rules[0]->conditions[0]->property);
$this->assertEquals(SegmentConditionOperator::IN, $context->segments[$overrideKey]->rules[0]->conditions[0]->operator);
$this->assertEquals(['overridden-id'], $context->segments[$overrideKey]->rules[0]->conditions[0]->value);

$this->assertEquals('', $context->segments[$overrideKey]->overrides[0]->key);
$this->assertEquals('some_feature', $context->segments[$overrideKey]->overrides[0]->name);
$this->assertFalse($context->segments[$overrideKey]->overrides[0]->enabled);
$this->assertEquals('some-overridden-value', $context->segments[$overrideKey]->overrides[0]->value);
$this->assertEquals(-INF, $context->segments[$overrideKey]->overrides[0]->priority);
$this->assertNull($context->segments[$overrideKey]->overrides[0]->variants);
$this->assertEquals(['id' => 1], $context->segments[$overrideKey]->overrides[0]->metadata);

$this->assertCount(3, $context->features);
$this->assertArrayHasKey('some_feature', $context->features);
$this->assertEquals('00000000-0000-0000-0000-000000000000', $context->features['some_feature']->key);
$this->assertEquals('some_feature', $context->features['some_feature']->name);
$this->assertTrue($context->features['some_feature']->enabled);
$this->assertEquals('some-value', $context->features['some_feature']->value);
$this->assertNull($context->features['some_feature']->priority);
$this->assertEmpty($context->features['some_feature']->variants);
$this->assertEquals(['id' => 1], $context->features['some_feature']->metadata);

// Test multivariate feature with IDs - priority should be based on ID
$this->assertArrayHasKey('mv_feature_with_ids', $context->features);
$mvFeatureWithIds = $context->features['mv_feature_with_ids'];
$this->assertEquals('2', $mvFeatureWithIds->key);
$this->assertEquals('mv_feature_with_ids', $mvFeatureWithIds->name);
$this->assertTrue($mvFeatureWithIds->enabled);
$this->assertEquals('default_value', $mvFeatureWithIds->value);
$this->assertNull($mvFeatureWithIds->priority);
$this->assertCount(2, $mvFeatureWithIds->variants);
$this->assertEquals(['id' => 2], $mvFeatureWithIds->metadata);

// First variant: ID=100, should have priority 100
$this->assertEquals('variant_a', $mvFeatureWithIds->variants[0]->value);
$this->assertEquals(30.0, $mvFeatureWithIds->variants[0]->weight);
$this->assertEquals(100, $mvFeatureWithIds->variants[0]->priority);

// Second variant: ID=200, should have priority 200
$this->assertEquals('variant_b', $mvFeatureWithIds->variants[1]->value);
$this->assertEquals(70.0, $mvFeatureWithIds->variants[1]->weight);
$this->assertEquals(200, $mvFeatureWithIds->variants[1]->priority);

// Test multivariate feature without IDs - priority should be based on UUID position
$this->assertArrayHasKey('mv_feature_without_ids', $context->features);
$mvFeatureWithoutIds = $context->features['mv_feature_without_ids'];
$this->assertEquals('3', $mvFeatureWithoutIds->key);
$this->assertEquals('mv_feature_without_ids', $mvFeatureWithoutIds->name);
$this->assertFalse($mvFeatureWithoutIds->enabled);
$this->assertEquals('fallback_value', $mvFeatureWithoutIds->value);
$this->assertNull($mvFeatureWithoutIds->priority);
$this->assertCount(3, $mvFeatureWithoutIds->variants);
$this->assertEquals(['id' => 3], $mvFeatureWithoutIds->metadata);

// Variants should be ordered by UUID alphabetically
$this->assertEquals('option_y', $mvFeatureWithoutIds->variants[0]->value);
$this->assertEquals(50.0, $mvFeatureWithoutIds->variants[0]->weight);
$this->assertEquals(1, $mvFeatureWithoutIds->variants[0]->priority); // Second
$this->assertEquals('option_x', $mvFeatureWithoutIds->variants[1]->value);
$this->assertEquals(25.0, $mvFeatureWithoutIds->variants[1]->weight);
$this->assertEquals(0, $mvFeatureWithoutIds->variants[1]->priority); // First
$this->assertEquals('option_z', $mvFeatureWithoutIds->variants[2]->value);
$this->assertEquals(25.0, $mvFeatureWithoutIds->variants[2]->weight);
$this->assertEquals(2, $mvFeatureWithoutIds->variants[2]->priority); // Third
// Then
$this->assertEquals(
$case['expected_evaluation_context'],
json_decode(json_encode($actual)),
);
}
}