packages/deriver/tests/Unit/Deriver/Evaluator/Intrinsic/IntrinsicEvaluationTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Intrinsic;
6
7use Deriver\Deriver\Evaluator\Intrinsic\IntrinsicDescriptor;
8use Deriver\Deriver\Evaluator\Intrinsic\IntrinsicEvaluation;
9use Deriver\Deriver\Evaluator\Intrinsic\PureIntrinsic;
10use Deriver\Deriver\Evaluator\TargetProfile;
11use Deriver\Deriver\Evaluator\Term;
12use Deriver\Deriver\Evaluator\TermKind;
13use Deriver\Exception\EvaluationContractException;
14use Deriver\Exception\EvaluationException;
15use Override;
16use PHPUnit\Framework\Attributes\CoversClass;
17use PHPUnit\Framework\Attributes\Small;
18use PHPUnit\Framework\Attributes\UsesClass;
19use PHPUnit\Framework\TestCase;
20
21#[CoversClass(IntrinsicEvaluation::class)]
22#[UsesClass(EvaluationException::class)]
23#[UsesClass(IntrinsicDescriptor::class)]
24#[UsesClass(TargetProfile::class)]
25#[UsesClass(Term::class)]
26#[Small]
27final class IntrinsicEvaluationTest extends TestCase
28{
29 public function testEvaluateRejectsAnArityMismatchAsAContractFailure(): void
30 {
31 $this->expectException(EvaluationContractException::class);
32 (new IntrinsicEvaluation())->evaluate(new class ('compose') implements PureIntrinsic {
33 /**
34 * @param string $operation compose, override, or uncertain-tags
35 */
36 public function __construct(public readonly string $operation)
37 {
38 }
39 /**
40 * @return IntrinsicDescriptor Explicit input dependencies
41 */
42 #[Override]
43 public function descriptor(): IntrinsicDescriptor
44 {
45 return new IntrinsicDescriptor('example.' . $this->operation, '1', 'policy.' . $this->operation, 2, [0, 1]);
46 }
47 /**
48 * @param list<Term> $arguments Policy operands
49 * @param TargetProfile $target Declared semantics
50 * @return Term Structured policy with independent fields
51 */
52 #[Override]
53 public function evaluate(array $arguments, TargetProfile $target): Term
54 {
55 [$left, $right] = $arguments;
56 if ($left->kind !== TermKind::Array || $right->kind !== TermKind::Array) {
57 return new Term(TermKind::Intrinsic, 'policy.' . $this->operation, $arguments, ['type' => 'array']);
58 }
59 if ($this->operation === 'override') {
60 return Term::array(array_replace($left->operands, $right->operands));
61 }
62 $ttl = $left->operands['ttl'] ?? Term::opaque('UNKNOWN_TTL');
63 $other = $right->operands['ttl'] ?? $ttl;
64 $ttl = is_int($ttl->literal) && is_int($other->literal) ? Term::constant(min($ttl->literal, $other->literal)) : new Term(TermKind::Intrinsic, 'policy.minimum', [$ttl, $other]);
65 $tags = $this->operation === 'uncertain-tags' ? Term::opaque('UNKNOWN_TAG_TRANSFORM') : Term::array(array_values([...$left->operands['tags']->operands ?? [], ...$right->operands['tags']->operands ?? []]));
66 return Term::array(['ttl' => $ttl, 'tags' => $tags]);
67 }
68 }, [], new TargetProfile());
69 }
70 public function testEvaluatePropagatesTheOriginalFailure(): void
71 {
72 $intrinsic = self::createStub(PureIntrinsic::class);
73 $intrinsic->method('descriptor')->willReturn(new IntrinsicDescriptor('test', '1', 'test', 1, [0]));
74 $failure = new EvaluationException('intrinsic implementation failed');
75 $intrinsic->method('evaluate')->willThrowException($failure);
76 $this->expectExceptionObject($failure);
77 (new IntrinsicEvaluation())->evaluate($intrinsic, [Term::parameter('x')], new TargetProfile());
78 }
79 public function testEvaluateRejectsIncompleteArguments(): void
80 {
81 $this->expectException(EvaluationContractException::class);
82 (new IntrinsicEvaluation())->evaluate(new class ('compose') implements PureIntrinsic {
83 /**
84 * @param string $operation compose, override, or uncertain-tags
85 */
86 public function __construct(public readonly string $operation)
87 {
88 }
89 /**
90 * @return IntrinsicDescriptor Explicit input dependencies
91 */
92 #[Override]
93 public function descriptor(): IntrinsicDescriptor
94 {
95 return new IntrinsicDescriptor('example.' . $this->operation, '1', 'policy.' . $this->operation, 2, [0, 1]);
96 }
97 /**
98 * @param list<Term> $arguments Policy operands
99 * @param TargetProfile $target Declared semantics
100 * @return Term Structured policy with independent fields
101 */
102 #[Override]
103 public function evaluate(array $arguments, TargetProfile $target): Term
104 {
105 [$left, $right] = $arguments;
106 if ($left->kind !== TermKind::Array || $right->kind !== TermKind::Array) {
107 return new Term(TermKind::Intrinsic, 'policy.' . $this->operation, $arguments, ['type' => 'array']);
108 }
109 if ($this->operation === 'override') {
110 return Term::array(array_replace($left->operands, $right->operands));
111 }
112 $ttl = $left->operands['ttl'] ?? Term::opaque('UNKNOWN_TTL');
113 $other = $right->operands['ttl'] ?? $ttl;
114 $ttl = is_int($ttl->literal) && is_int($other->literal) ? Term::constant(min($ttl->literal, $other->literal)) : new Term(TermKind::Intrinsic, 'policy.minimum', [$ttl, $other]);
115 $tags = $this->operation === 'uncertain-tags' ? Term::opaque('UNKNOWN_TAG_TRANSFORM') : Term::array(array_values([...$left->operands['tags']->operands ?? [], ...$right->operands['tags']->operands ?? []]));
116 return Term::array(['ttl' => $ttl, 'tags' => $tags]);
117 }
118 }, [Term::parameter('x')], new TargetProfile());
119 }
120 public function testEvaluateReturnsTheIntrinsicResult(): void
121 {
122 $intrinsic = self::createStub(PureIntrinsic::class);
123 $intrinsic->method('descriptor')->willReturn(new IntrinsicDescriptor('test', '1', 'test', 1, [0]));
124 $intrinsic->method('evaluate')->willReturn(Term::constant('derived private text'));
125 $result = (new IntrinsicEvaluation())->evaluate($intrinsic, [Term::constant('private')], new TargetProfile());
126 self::assertSame('derived private text', $result->native());
127 }
128 public function testEvaluatePreservesTheReturnedTerm(): void
129 {
130 $intrinsic = self::createStub(PureIntrinsic::class);
131 $intrinsic->method('descriptor')->willReturn(new IntrinsicDescriptor('test', '1', 'test', 1, [0]));
132 $value = Term::constant('public');
133 $intrinsic->method('evaluate')->willReturn($value);
134 self::assertSame($value, (new IntrinsicEvaluation())->evaluate($intrinsic, [Term::constant('input')], new TargetProfile()));
135 }
136}
137