diff --git a/src/Models/LedgerItem.php b/src/Models/LedgerItem.php new file mode 100644 index 0000000..9fa652c --- /dev/null +++ b/src/Models/LedgerItem.php @@ -0,0 +1,30 @@ +client = $client; + $this->id = $data['id'] ?? null; + $this->createdAt = $data['created_at'] ?? null; + $this->amount = $data['amount'] ?? null; + $this->kind = $data['kind'] ?? null; + $this->metadata = $data['metadata'] ?? null; + + $this->accessPass = isset($data['access_pass']) + ? new LedgerItemAccessPass($client, $data['access_pass']) + : null; + } +} diff --git a/src/Models/LedgerItemAccessPass.php b/src/Models/LedgerItemAccessPass.php new file mode 100644 index 0000000..1c2797c --- /dev/null +++ b/src/Models/LedgerItemAccessPass.php @@ -0,0 +1,30 @@ +client = $client; + $this->id = $data['id'] ?? null; + $this->fullName = $data['full_name'] ?? null; + $this->state = $data['state'] ?? null; + $this->metadata = $data['metadata'] ?? null; + $this->unifiedAccessPassExId = $data['unified_access_pass_ex_id'] ?? null; + + $this->passTemplate = isset($data['pass_template']) + ? new LedgerItemPassTemplate($client, $data['pass_template']) + : null; + } +} diff --git a/src/Models/LedgerItemPassTemplate.php b/src/Models/LedgerItemPassTemplate.php new file mode 100644 index 0000000..8dfed8c --- /dev/null +++ b/src/Models/LedgerItemPassTemplate.php @@ -0,0 +1,25 @@ +client = $client; + $this->id = $data['id'] ?? null; + $this->name = $data['name'] ?? null; + $this->protocol = $data['protocol'] ?? null; + $this->platform = $data['platform'] ?? null; + $this->useCase = $data['use_case'] ?? null; + } +} diff --git a/src/Models/PassTemplatePair.php b/src/Models/PassTemplatePair.php new file mode 100644 index 0000000..92f13a0 --- /dev/null +++ b/src/Models/PassTemplatePair.php @@ -0,0 +1,31 @@ +client = $client; + $this->id = $data['id'] ?? null; + $this->name = $data['name'] ?? null; + $this->createdAt = $data['created_at'] ?? null; + + $this->androidTemplate = isset($data['android_template']) + ? new TemplateInfo($client, $data['android_template']) + : null; + + $this->iosTemplate = isset($data['ios_template']) + ? new TemplateInfo($client, $data['ios_template']) + : null; + } +} diff --git a/src/Models/TemplateInfo.php b/src/Models/TemplateInfo.php new file mode 100644 index 0000000..05d0c6f --- /dev/null +++ b/src/Models/TemplateInfo.php @@ -0,0 +1,21 @@ +client = $client; + $this->id = $data['id'] ?? null; + $this->name = $data['name'] ?? null; + $this->platform = $data['platform'] ?? null; + } +} diff --git a/src/Services/Console.php b/src/Services/Console.php index 6cb6ac3..0def75f 100644 --- a/src/Services/Console.php +++ b/src/Services/Console.php @@ -4,6 +4,8 @@ use AccessGrid\AccessGridClient; use AccessGrid\Models\Template; +use AccessGrid\Models\PassTemplatePair; +use AccessGrid\Models\LedgerItem; class Console { @@ -70,6 +72,40 @@ public function eventLog(array $data): array }, $events); } + /** + * List pass template pairs + */ + public function listPassTemplatePairs(array $params = []): array + { + $response = $this->client->get('/v1/console/pass-template-pairs', $params); + + if (isset($response['pass_template_pairs'])) { + $response['pass_template_pairs'] = array_map( + fn($pair) => new PassTemplatePair($this->client, $pair), + $response['pass_template_pairs'] + ); + } + + return $response; + } + + /** + * List ledger items + */ + public function listLedgerItems(array $params = []): array + { + $response = $this->client->get('/v1/console/ledger-items', $params); + + if (isset($response['ledger_items'])) { + $response['ledger_items'] = array_map( + fn($item) => new LedgerItem($this->client, $item), + $response['ledger_items'] + ); + } + + return $response; + } + /** * Get iOS provisioning identifiers for preflight */ diff --git a/tests/Models/LedgerItemTest.php b/tests/Models/LedgerItemTest.php new file mode 100644 index 0000000..e7b08bb --- /dev/null +++ b/tests/Models/LedgerItemTest.php @@ -0,0 +1,172 @@ + '2025-06-15T10:30:00Z', + 'amount' => 1.50, + 'id' => 'li_123', + 'ex_id' => 'li_123', + 'kind' => 'access_pass_issued', + 'metadata' => ['access_pass_ex_id' => 'pass_456'], + 'access_pass' => [ + 'id' => 'pass_456', + 'ex_id' => 'pass_456', + 'full_name' => 'Jane Doe', + 'state' => 'active', + 'metadata' => ['department' => 'engineering'], + 'unified_access_pass_ex_id' => 'uap_789', + 'pass_template' => [ + 'id' => 'tmpl_abc', + 'ex_id' => 'tmpl_abc', + 'name' => 'Employee Badge', + 'protocol' => 'desfire', + 'platform' => 'apple', + 'use_case' => 'employee_badge', + ], + ], + ]; + + $item = new LedgerItem($this->client, $data); + + $this->assertEquals('li_123', $item->id); + $this->assertEquals('2025-06-15T10:30:00Z', $item->createdAt); + $this->assertEquals(1.50, $item->amount); + $this->assertEquals('access_pass_issued', $item->kind); + $this->assertEquals('pass_456', $item->metadata['access_pass_ex_id']); + + $this->assertInstanceOf(LedgerItemAccessPass::class, $item->accessPass); + $this->assertEquals('pass_456', $item->accessPass->id); + $this->assertEquals('Jane Doe', $item->accessPass->fullName); + $this->assertEquals('active', $item->accessPass->state); + $this->assertEquals('engineering', $item->accessPass->metadata['department']); + $this->assertEquals('uap_789', $item->accessPass->unifiedAccessPassExId); + + $this->assertInstanceOf(LedgerItemPassTemplate::class, $item->accessPass->passTemplate); + $this->assertEquals('tmpl_abc', $item->accessPass->passTemplate->id); + $this->assertEquals('Employee Badge', $item->accessPass->passTemplate->name); + $this->assertEquals('desfire', $item->accessPass->passTemplate->protocol); + $this->assertEquals('apple', $item->accessPass->passTemplate->platform); + $this->assertEquals('employee_badge', $item->accessPass->passTemplate->useCase); + } + + public function testConstructionWithNullAccessPass(): void + { + $data = [ + 'created_at' => '2025-06-15T10:30:00Z', + 'amount' => 0.75, + 'id' => 'li_no_pass', + 'ex_id' => 'li_no_pass', + 'kind' => 'adjustment', + 'metadata' => [], + 'access_pass' => null, + ]; + + $item = new LedgerItem($this->client, $data); + + $this->assertEquals('li_no_pass', $item->id); + $this->assertEquals(0.75, $item->amount); + $this->assertEquals('adjustment', $item->kind); + $this->assertNull($item->accessPass); + } + + public function testConstructionWithAccessPassButNoPassTemplate(): void + { + $data = [ + 'created_at' => '2025-06-15T10:30:00Z', + 'amount' => 1.00, + 'id' => 'li_no_tmpl', + 'ex_id' => 'li_no_tmpl', + 'kind' => 'access_pass_issued', + 'metadata' => [], + 'access_pass' => [ + 'id' => 'pass_456', + 'ex_id' => 'pass_456', + 'full_name' => 'John Smith', + 'state' => 'active', + 'metadata' => [], + ], + ]; + + $item = new LedgerItem($this->client, $data); + + $this->assertInstanceOf(LedgerItemAccessPass::class, $item->accessPass); + $this->assertEquals('pass_456', $item->accessPass->id); + $this->assertEquals('John Smith', $item->accessPass->fullName); + $this->assertNull($item->accessPass->passTemplate); + $this->assertNull($item->accessPass->unifiedAccessPassExId); + } + + public function testConstructionWithMinimalData(): void + { + $item = new LedgerItem($this->client, [ + 'id' => 'li_minimal', + 'ex_id' => 'li_minimal', + ]); + + $this->assertEquals('li_minimal', $item->id); + $this->assertNull($item->createdAt); + $this->assertNull($item->amount); + $this->assertNull($item->kind); + $this->assertNull($item->metadata); + $this->assertNull($item->accessPass); + } + + public function testConstructionWithEmptyData(): void + { + $item = new LedgerItem($this->client, []); + + $this->assertNull($item->id); + $this->assertNull($item->createdAt); + $this->assertNull($item->amount); + $this->assertNull($item->kind); + $this->assertNull($item->metadata); + $this->assertNull($item->accessPass); + } + + public function testIdReadsFromId(): void + { + $item = new LedgerItem($this->client, [ + 'id' => 'li_123', + ]); + + $this->assertEquals('li_123', $item->id); + } + + public function testAccessPassIdReadsFromId(): void + { + $item = new LedgerItem($this->client, [ + 'access_pass' => [ + 'id' => 'pass_123', + 'full_name' => 'Test User', + 'state' => 'active', + ], + ]); + + $this->assertEquals('pass_123', $item->accessPass->id); + } + + public function testPassTemplateIdReadsFromId(): void + { + $item = new LedgerItem($this->client, [ + 'access_pass' => [ + 'id' => 'pass_123', + 'pass_template' => [ + 'id' => 'tmpl_456', + 'name' => 'Badge', + ], + ], + ]); + + $this->assertEquals('tmpl_456', $item->accessPass->passTemplate->id); + } +} diff --git a/tests/Models/PassTemplatePairTest.php b/tests/Models/PassTemplatePairTest.php new file mode 100644 index 0000000..a9a9f35 --- /dev/null +++ b/tests/Models/PassTemplatePairTest.php @@ -0,0 +1,139 @@ + 'pair_123', + 'name' => 'Employee Badge Pair', + 'created_at' => '2025-01-01T00:00:00Z', + 'android_template' => [ + 'id' => 'tmpl_android_456', + 'name' => 'Employee Badge Android', + 'platform' => 'android', + ], + 'ios_template' => [ + 'id' => 'tmpl_ios_789', + 'name' => 'Employee Badge iOS', + 'platform' => 'apple', + ], + ]; + + $pair = new PassTemplatePair($this->client, $data); + + $this->assertEquals('pair_123', $pair->id); + $this->assertEquals('Employee Badge Pair', $pair->name); + $this->assertEquals('2025-01-01T00:00:00Z', $pair->createdAt); + + $this->assertInstanceOf(TemplateInfo::class, $pair->androidTemplate); + $this->assertEquals('tmpl_android_456', $pair->androidTemplate->id); + $this->assertEquals('Employee Badge Android', $pair->androidTemplate->name); + $this->assertEquals('android', $pair->androidTemplate->platform); + + $this->assertInstanceOf(TemplateInfo::class, $pair->iosTemplate); + $this->assertEquals('tmpl_ios_789', $pair->iosTemplate->id); + $this->assertEquals('Employee Badge iOS', $pair->iosTemplate->name); + $this->assertEquals('apple', $pair->iosTemplate->platform); + } + + public function testConstructionWithIosOnly(): void + { + $data = [ + 'id' => 'pair_ios_only', + 'name' => 'iOS Only Pair', + 'ios_template' => [ + 'id' => 'tmpl_ios', + 'name' => 'iOS Template', + 'platform' => 'apple', + ], + ]; + + $pair = new PassTemplatePair($this->client, $data); + + $this->assertInstanceOf(TemplateInfo::class, $pair->iosTemplate); + $this->assertEquals('tmpl_ios', $pair->iosTemplate->id); + $this->assertNull($pair->androidTemplate); + } + + public function testConstructionWithAndroidOnly(): void + { + $data = [ + 'id' => 'pair_android_only', + 'name' => 'Android Only Pair', + 'android_template' => [ + 'id' => 'tmpl_android', + 'name' => 'Android Template', + 'platform' => 'android', + ], + ]; + + $pair = new PassTemplatePair($this->client, $data); + + $this->assertInstanceOf(TemplateInfo::class, $pair->androidTemplate); + $this->assertEquals('tmpl_android', $pair->androidTemplate->id); + $this->assertNull($pair->iosTemplate); + } + + public function testConstructionWithMinimalData(): void + { + $pair = new PassTemplatePair($this->client, ['id' => 'pair_minimal']); + + $this->assertEquals('pair_minimal', $pair->id); + $this->assertNull($pair->name); + $this->assertNull($pair->createdAt); + $this->assertNull($pair->androidTemplate); + $this->assertNull($pair->iosTemplate); + } + + public function testConstructionWithEmptyData(): void + { + $pair = new PassTemplatePair($this->client, []); + + $this->assertNull($pair->id); + $this->assertNull($pair->name); + $this->assertNull($pair->createdAt); + $this->assertNull($pair->androidTemplate); + $this->assertNull($pair->iosTemplate); + } +} + +class TemplateInfoTest extends TestCase +{ + public function testConstructionWithFullData(): void + { + $info = new TemplateInfo($this->client, [ + 'id' => 'tmpl_info_123', + 'name' => 'Template Name', + 'platform' => 'apple', + ]); + + $this->assertEquals('tmpl_info_123', $info->id); + $this->assertEquals('Template Name', $info->name); + $this->assertEquals('apple', $info->platform); + } + + public function testConstructionWithMinimalData(): void + { + $info = new TemplateInfo($this->client, ['id' => 'tmpl_minimal']); + + $this->assertEquals('tmpl_minimal', $info->id); + $this->assertNull($info->name); + $this->assertNull($info->platform); + } + + public function testConstructionWithEmptyData(): void + { + $info = new TemplateInfo($this->client, []); + + $this->assertNull($info->id); + $this->assertNull($info->name); + $this->assertNull($info->platform); + } +} diff --git a/tests/Services/ConsoleTest.php b/tests/Services/ConsoleTest.php index 714dcab..38472a6 100644 --- a/tests/Services/ConsoleTest.php +++ b/tests/Services/ConsoleTest.php @@ -4,6 +4,11 @@ use AccessGrid\Tests\TestCase; use AccessGrid\Models\Template; +use AccessGrid\Models\PassTemplatePair; +use AccessGrid\Models\TemplateInfo; +use AccessGrid\Models\LedgerItem; +use AccessGrid\Models\LedgerItemAccessPass; +use AccessGrid\Models\LedgerItemPassTemplate; class ConsoleTest extends TestCase { @@ -181,4 +186,220 @@ public function testIosPreflight(): void $this->assertEquals('share_456', $result->sharingInstanceIdentifier); $this->assertEquals('production', $result->environmentIdentifier); } + + public function testListPassTemplatePairs(): void + { + $this->expectRequest('GET', '/v1/console/pass-template-pairs', 200, [ + 'pass_template_pairs' => [ + [ + 'id' => 'pair_123', + 'name' => 'Employee Badge Pair', + 'created_at' => '2025-01-01T00:00:00Z', + 'android_template' => [ + 'id' => 'tmpl_android_456', + 'name' => 'Employee Badge Android', + 'platform' => 'android', + ], + 'ios_template' => [ + 'id' => 'tmpl_ios_789', + 'name' => 'Employee Badge iOS', + 'platform' => 'apple', + ], + ], + ], + 'pagination' => [ + 'current_page' => 1, + 'per_page' => 25, + 'total_pages' => 1, + 'total_count' => 1, + ], + ]); + + $result = $this->client->console->listPassTemplatePairs(); + + $this->assertArrayHasKey('pass_template_pairs', $result); + $this->assertArrayHasKey('pagination', $result); + $this->assertCount(1, $result['pass_template_pairs']); + + $pair = $result['pass_template_pairs'][0]; + $this->assertInstanceOf(PassTemplatePair::class, $pair); + $this->assertEquals('pair_123', $pair->id); + $this->assertEquals('Employee Badge Pair', $pair->name); + + $this->assertInstanceOf(TemplateInfo::class, $pair->androidTemplate); + $this->assertEquals('tmpl_android_456', $pair->androidTemplate->id); + + $this->assertInstanceOf(TemplateInfo::class, $pair->iosTemplate); + $this->assertEquals('tmpl_ios_789', $pair->iosTemplate->id); + + $this->assertEquals(1, $result['pagination']['current_page']); + } + + public function testListPassTemplatePairsWithParams(): void + { + $this->mockHttpClient + ->expects($this->once()) + ->method('send') + ->with( + $this->equalTo('GET'), + $this->callback(function (string $url) { + return strpos($url, '/v1/console/pass-template-pairs') !== false + && strpos($url, 'page=2') !== false + && strpos($url, 'per_page=10') !== false; + }), + $this->anything(), + $this->anything() + ) + ->willReturn(new \AccessGrid\Http\HttpResponse(200, json_encode([ + 'pass_template_pairs' => [], + 'pagination' => ['current_page' => 2, 'per_page' => 10, 'total_pages' => 3, 'total_count' => 25], + ]))); + + $result = $this->client->console->listPassTemplatePairs(['page' => 2, 'per_page' => 10]); + + $this->assertArrayHasKey('pass_template_pairs', $result); + $this->assertCount(0, $result['pass_template_pairs']); + $this->assertEquals(2, $result['pagination']['current_page']); + } + + public function testListPassTemplatePairsEmpty(): void + { + $this->expectRequest('GET', '/v1/console/pass-template-pairs', 200, [ + 'pass_template_pairs' => [], + 'pagination' => ['current_page' => 1, 'per_page' => 25, 'total_pages' => 0, 'total_count' => 0], + ]); + + $result = $this->client->console->listPassTemplatePairs(); + + $this->assertCount(0, $result['pass_template_pairs']); + } + + public function testListLedgerItems(): void + { + $this->expectRequest('GET', '/v1/console/ledger-items', 200, [ + 'ledger_items' => [ + [ + 'created_at' => '2025-06-15T10:30:00Z', + 'amount' => 1.50, + 'id' => 'li_123', + 'ex_id' => 'li_123', + 'kind' => 'access_pass_issued', + 'metadata' => ['access_pass_ex_id' => 'pass_456'], + 'access_pass' => [ + 'id' => 'pass_456', + 'ex_id' => 'pass_456', + 'full_name' => 'Jane Doe', + 'state' => 'active', + 'metadata' => [], + 'pass_template' => [ + 'id' => 'tmpl_abc', + 'ex_id' => 'tmpl_abc', + 'name' => 'Employee Badge', + 'protocol' => 'desfire', + 'platform' => 'apple', + 'use_case' => 'employee_badge', + ], + ], + ], + ], + 'pagination' => [ + 'current_page' => 1, + 'per_page' => 50, + 'total_pages' => 1, + 'total_count' => 1, + ], + ]); + + $result = $this->client->console->listLedgerItems(); + + $this->assertArrayHasKey('ledger_items', $result); + $this->assertArrayHasKey('pagination', $result); + $this->assertCount(1, $result['ledger_items']); + + $item = $result['ledger_items'][0]; + $this->assertInstanceOf(LedgerItem::class, $item); + $this->assertEquals('li_123', $item->id); + $this->assertEquals(1.50, $item->amount); + $this->assertEquals('access_pass_issued', $item->kind); + + $this->assertInstanceOf(LedgerItemAccessPass::class, $item->accessPass); + $this->assertEquals('pass_456', $item->accessPass->id); + $this->assertEquals('Jane Doe', $item->accessPass->fullName); + + $this->assertInstanceOf(LedgerItemPassTemplate::class, $item->accessPass->passTemplate); + $this->assertEquals('tmpl_abc', $item->accessPass->passTemplate->id); + $this->assertEquals('Employee Badge', $item->accessPass->passTemplate->name); + + $this->assertEquals(1, $result['pagination']['current_page']); + } + + public function testListLedgerItemsWithParams(): void + { + $this->mockHttpClient + ->expects($this->once()) + ->method('send') + ->with( + $this->equalTo('GET'), + $this->callback(function (string $url) { + return strpos($url, '/v1/console/ledger-items') !== false + && strpos($url, 'page=2') !== false + && strpos($url, 'per_page=10') !== false + && strpos($url, 'start_date=2025-01-01T00%3A00%3A00Z') !== false + && strpos($url, 'end_date=2025-06-30T23%3A59%3A59Z') !== false; + }), + $this->anything(), + $this->anything() + ) + ->willReturn(new \AccessGrid\Http\HttpResponse(200, json_encode([ + 'ledger_items' => [], + 'pagination' => ['current_page' => 2, 'per_page' => 10, 'total_pages' => 5, 'total_count' => 42], + ]))); + + $result = $this->client->console->listLedgerItems([ + 'page' => 2, + 'per_page' => 10, + 'start_date' => '2025-01-01T00:00:00Z', + 'end_date' => '2025-06-30T23:59:59Z', + ]); + + $this->assertArrayHasKey('ledger_items', $result); + $this->assertCount(0, $result['ledger_items']); + $this->assertEquals(2, $result['pagination']['current_page']); + } + + public function testListLedgerItemsWithNullAccessPass(): void + { + $this->expectRequest('GET', '/v1/console/ledger-items', 200, [ + 'ledger_items' => [ + [ + 'created_at' => '2025-06-15T10:30:00Z', + 'amount' => 0.75, + 'id' => 'li_no_pass', + 'ex_id' => 'li_no_pass', + 'kind' => 'adjustment', + 'metadata' => [], + 'access_pass' => null, + ], + ], + 'pagination' => ['current_page' => 1, 'per_page' => 50, 'total_pages' => 1, 'total_count' => 1], + ]); + + $result = $this->client->console->listLedgerItems(); + + $item = $result['ledger_items'][0]; + $this->assertInstanceOf(LedgerItem::class, $item); + $this->assertNull($item->accessPass); + } + + public function testListLedgerItemsEmpty(): void + { + $this->expectRequest('GET', '/v1/console/ledger-items', 200, [ + 'ledger_items' => [], + 'pagination' => ['current_page' => 1, 'per_page' => 50, 'total_pages' => 0, 'total_count' => 0], + ]); + + $result = $this->client->console->listLedgerItems(); + + $this->assertCount(0, $result['ledger_items']); + } }