Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
AccessDelegationStoreContractTests
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
3 / 3
3
100.00% covered (success)
100.00%
1 / 1
 createStore
n/a
0 / 0
n/a
0 / 0
0
 testGrantsAreKeptPerAgentAndObjectWithTheirDetails
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 testRevokeFromWithdrawsWhatOneRequestGranted
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 testEraseForDropsEveryGrantOnOneObject
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Testing\Contract;
6
7use DateTimeImmutable;
8use LambdaTwelve\OneRecord\Api\Permission;
9use LambdaTwelve\OneRecord\Rdf\Iri;
10use LambdaTwelve\OneRecord\Server\Spi\AccessDelegationStore;
11use LambdaTwelve\OneRecord\Server\Spi\Grant;
12
13/**
14 * The tests behind AccessDelegationStoreContract, as a trait, for hosts whose test cases must
15 * extend a framework base class (Laravel's Testbench, Drupal's KernelTestBase)
16 * and so cannot extend the abstract contract. Use it in any PHPUnit TestCase
17 * and implement the abstract hook(s); the abstract class is this trait on a
18 * bare TestCase.
19 */
20trait AccessDelegationStoreContractTests
21{
22    protected const string CONTRACT_OBJECT = 'https://1r.example.com/logistics-objects/p1';
23    protected const string CONTRACT_OTHER = 'https://1r.example.com/logistics-objects/p2';
24    protected const string CONTRACT_PARTNER = 'https://1r.partner.example/logistics-objects/partner';
25    protected const string CONTRACT_STRANGER = 'https://1r.other.example/logistics-objects/other';
26    protected const string CONTRACT_REQUEST = 'https://1r.example.com/action-requests/d1';
27
28    abstract protected function createStore(): AccessDelegationStore;
29
30    public function testGrantsAreKeptPerAgentAndObjectWithTheirDetails(): void
31    {
32        $store = $this->createStore();
33        self::assertSame([], $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT)));
34
35        $expires = new DateTimeImmutable('2026-12-31T23:59:59.000Z');
36        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT), [Permission::GetLogisticsObject]));
37        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT), [Permission::PatchLogisticsObject, Permission::PostLogisticsEvent], $expires, new Iri(self::CONTRACT_REQUEST)));
38        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OTHER), [Permission::GetLogisticsObject]));
39        $store->grant(new Grant(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OBJECT), [Permission::GetLogisticsEvent]));
40
41        $grants = $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT));
42        self::assertCount(2, $grants);
43        $sourced = array_values(array_filter($grants, static fn(Grant $g): bool => $g->source !== null));
44        self::assertCount(1, $sourced);
45        self::assertSame(self::CONTRACT_REQUEST, $sourced[0]->source?->value);
46        self::assertSame($expires->format(DATE_RFC3339_EXTENDED), $sourced[0]->expiresAt?->format(DATE_RFC3339_EXTENDED));
47        self::assertTrue($sourced[0]->allows(Permission::PatchLogisticsObject));
48        self::assertFalse($sourced[0]->allows(Permission::GetLogisticsObject));
49        self::assertTrue($sourced[0]->isActiveAt(new DateTimeImmutable('2026-10-02T12:00:00Z')));
50        self::assertFalse($sourced[0]->isActiveAt(new DateTimeImmutable('2027-01-01T00:00:00Z')));
51        self::assertCount(1, $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OTHER)));
52        self::assertCount(1, $store->grantsFor(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OBJECT)));
53        self::assertSame([], $store->grantsFor(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OTHER)));
54    }
55
56    public function testRevokeFromWithdrawsWhatOneRequestGranted(): void
57    {
58        $store = $this->createStore();
59        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT), [Permission::GetLogisticsObject]));
60        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT), [Permission::PatchLogisticsObject], null, new Iri(self::CONTRACT_REQUEST)));
61        $store->grant(new Grant(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OTHER), [Permission::GetLogisticsObject], null, new Iri(self::CONTRACT_REQUEST)));
62
63        $store->revokeFrom(new Iri(self::CONTRACT_REQUEST));
64
65        $left = $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT));
66        self::assertCount(1, $left, 'the host\'s own grant stays');
67        self::assertNull($left[0]->source);
68        self::assertSame([], $store->grantsFor(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OTHER)), 'every grant of the request, on any object');
69        $store->revokeFrom(new Iri('https://1r.example.com/action-requests/unknown'));
70    }
71
72    public function testEraseForDropsEveryGrantOnOneObject(): void
73    {
74        $store = $this->createStore();
75        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT), [Permission::GetLogisticsObject]));
76        $store->grant(new Grant(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OBJECT), [Permission::GetLogisticsObject], null, new Iri(self::CONTRACT_REQUEST)));
77        $store->grant(new Grant(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OTHER), [Permission::GetLogisticsObject]));
78
79        $store->eraseFor(new Iri(self::CONTRACT_OBJECT));
80
81        self::assertSame([], $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OBJECT)));
82        self::assertSame([], $store->grantsFor(new Iri(self::CONTRACT_STRANGER), new Iri(self::CONTRACT_OBJECT)));
83        self::assertCount(1, $store->grantsFor(new Iri(self::CONTRACT_PARTNER), new Iri(self::CONTRACT_OTHER)));
84    }
85}