diff --git a/docs/printnode/account-management.md b/docs/printnode/account-management.md new file mode 100644 index 0000000..ccc4d11 --- /dev/null +++ b/docs/printnode/account-management.md @@ -0,0 +1,239 @@ +# Account Management + +PrintNode offers Integrator Accounts, enabling you to programmatically create and manage separate PrintNode accounts for your customers, referred to as Child Accounts. Each Child Account operates independently with its own credentials but remains under the control of the Integrator Account that created it. + +## Prerequisites + +To utilize account management features, you need to upgrade your existing PrintNode account to an Integrator Account. This upgrade can be initiated through the [PrintNode web application](https://api.printnode.com/app/integrators/upgrade). + +## Basic Usage + +### Creating a Child Account + +```php +use Rawilk\Printing\Facades\Printing; + +$client = Printing::driver('printnode')->client(); + +$childAccount = $client->accounts->create([ + 'Account' => [ + 'email' => 'customer@example.com', + 'password' => 'securepassword123', + 'creatorRef' => 'unique-customer-reference', + ], + 'ApiKeys' => ['development', 'production'], + 'Tags' => [ + 'customerType' => 'premium', + 'region' => 'us-west', + ], +]); + +echo "Created child account with ID: " . $childAccount->id; +``` + +### Retrieving an Account + +```php +$accountId = 12345; +$account = $client->accounts->retrieve($accountId); + +echo "Account email: " . $account->email; +echo "Account state: " . $account->state; +echo "Can create sub-accounts: " . ($account->canCreateSubAccounts() ? 'Yes' : 'No'); +``` + +### Updating an Account + +```php +$accountId = 12345; +$updatedAccount = $client->accounts->update($accountId, [ + 'Account' => [ + 'email' => 'newemail@example.com', + ], + 'Tags' => [ + 'customerType' => 'enterprise', + 'priority' => 'high', + ], +]); +``` + +### Listing Child Accounts + +```php +// Get all child accounts +$childAccounts = $client->accounts->all(); + +foreach ($childAccounts as $account) { + echo "Account ID: {$account->id}, Email: {$account->email}\n"; +} + +// Download detailed account data +$detailedAccounts = $client->accounts->download(); +``` + +### Account Management Operations + +#### Suspending an Account + +```php +$accountId = 12345; +$suspendedAccount = $client->accounts->suspend($accountId); + +echo "Account state: " . $suspendedAccount->state; // 'suspended' +``` + +#### Activating an Account + +```php +$accountId = 12345; +$activeAccount = $client->accounts->activate($accountId); + +echo "Account state: " . $activeAccount->state; // 'active' +``` + +#### Adding Tags + +```php +$accountId = 12345; +$account = $client->accounts->addTags($accountId, [ + 'newTag' => 'newValue', + 'category' => 'special', +]); +``` + +#### Generating API Keys + +```php +$accountId = 12345; +$account = $client->accounts->generateApiKeys($accountId, ['staging', 'testing']); + +// Access the generated API keys +$apiKeys = $account->getApiKeys(); +foreach ($apiKeys as $key) { + echo "Key name: {$key['name']}, Key: {$key['key']}\n"; +} +``` + +#### Deleting an Account + +```php +$accountId = 12345; +$deletedAccount = $client->accounts->delete($accountId); +``` + +## Account Resource Properties + +The `Account` resource provides access to the following properties: + +- `id`: The account's unique identifier +- `firstname`: Account holder's first name (deprecated, usually "-") +- `lastname`: Account holder's last name (deprecated, usually "-") +- `email`: Account holder's email address +- `canCreateSubAccounts`: Whether this account can create sub-accounts +- `creatorEmail`: Email of the account that created this sub-account +- `creatorRef`: Creation reference set when the account was created +- `childAccounts`: Array of child accounts (for integrator accounts) +- `credits`: Number of print credits remaining +- `numComputers`: Number of active computers +- `totalPrints`: Total number of prints made +- `Tags`: Collection of tags set on the account +- `ApiKeys`: Collection of API keys for the account +- `state`: Account status ('active', 'suspended', etc.) +- `permissions`: Account permissions array + +## Helper Methods + +The `Account` resource provides several helper methods: + +```php +// Check if account is active +if ($account->isActive()) { + echo "Account is active"; +} + +// Check if account can create sub-accounts (integrator account) +if ($account->canCreateSubAccounts()) { + echo "This is an integrator account"; +} + +// Get child accounts +$children = $account->getChildAccounts(); + +// Check if account has child accounts +if ($account->hasChildAccounts()) { + echo "This account has " . count($account->getChildAccounts()) . " child accounts"; +} + +// Get creator reference +$creatorRef = $account->getCreatorRef(); + +// Get tags +$tags = $account->getTags(); + +// Get API keys +$apiKeys = $account->getApiKeys(); +``` + +## Account Creation Parameters + +When creating a child account, you can specify the following parameters: + +### Required Parameters (Account object) +- `email`: Contact email address for the customer +- `password`: Password of at least 8 characters + +### Optional Parameters (Account object) +- `firstname`: Deprecated field, defaults to "-" +- `lastname`: Deprecated field, defaults to "-" +- `creatorRef`: Unique reference to identify the account + +### Optional Top-level Parameters +- `ApiKeys`: Array of API key names to generate upon creation +- `Tags`: Object containing tag names and corresponding values + +## Error Handling + +Account operations may throw exceptions for various reasons: + +```php +use Rawilk\Printing\Api\PrintNode\Exceptions\PrintNodeApiRequestFailed; +use Rawilk\Printing\Api\PrintNode\Exceptions\AuthenticationFailure; + +try { + $account = $client->accounts->create([ + 'Account' => [ + 'email' => 'customer@example.com', + 'password' => 'weak', // Too short + ], + ]); +} catch (PrintNodeApiRequestFailed $e) { + echo "Account creation failed: " . $e->getMessage(); +} catch (AuthenticationFailure $e) { + echo "Authentication failed - check your API key"; +} +``` + +## Best Practices + +1. **Use meaningful creator references**: Set unique `creatorRef` values to easily identify and manage child accounts +2. **Implement proper error handling**: Always wrap account operations in try-catch blocks +3. **Use tags for organization**: Leverage tags to categorize and organize child accounts +4. **Manage API keys securely**: Generate separate API keys for different environments (development, production) +5. **Monitor account states**: Regularly check account states and handle suspended accounts appropriately +6. **Implement proper authentication**: Ensure your integrator API key is kept secure and has appropriate permissions + +## Integration with Whoami + +The existing `Whoami` service will show integrator account information including child accounts: + +```php +$whoami = $client->whoami->check(); + +if ($whoami->canCreateSubAccounts) { + echo "This is an integrator account with " . count($whoami->childAccounts) . " child accounts"; + + foreach ($whoami->childAccounts as $child) { + echo "Child account: {$child['email']} (ID: {$child['id']})\n"; + } +} +``` \ No newline at end of file diff --git a/docs/printnode/api.md b/docs/printnode/api.md index eff1c44..d2cd1dc 100644 --- a/docs/printnode/api.md +++ b/docs/printnode/api.md @@ -51,6 +51,16 @@ For example, to retrieve all printers for an account, you would use the `printer $client->printers->all(); ``` +### Available Services + +The following services are available on the PrintNode client: + +- **accounts**: Manage integrator and child accounts +- **computers**: Retrieve computer information +- **printers**: Manage and retrieve printer information +- **printJobs**: Create and manage print jobs +- **whoami**: Get account information + More information about each service can be found on that service's doc page. ## Resources diff --git a/examples/account-management.php b/examples/account-management.php new file mode 100644 index 0000000..f0eb456 --- /dev/null +++ b/examples/account-management.php @@ -0,0 +1,170 @@ +client(); + +// Check if current account is an integrator account +$whoami = $client->whoami->check(); + +if ($whoami->canCreateSubAccounts()) { + echo "This is an integrator account!\n"; + echo "Current child accounts: " . count($whoami->childAccounts) . "\n"; +} else { + echo "This account cannot create sub-accounts. Upgrade to an integrator account.\n"; + exit; +} + +// Create a new child account +try { + $newAccount = $client->accounts->create([ + 'Account' => [ + 'email' => 'customer@example.com', + 'password' => 'securepassword123', + 'creatorRef' => 'customer-' . uniqid(), + ], + 'ApiKeys' => ['development', 'production'], + 'Tags' => [ + 'customerType' => 'premium', + 'region' => 'us-west', + 'createdAt' => date('Y-m-d H:i:s'), + ], + ]); + + echo "Created new child account:\n"; + echo "- ID: {$newAccount->id}\n"; + echo "- Email: {$newAccount->email}\n"; + echo "- Creator Ref: {$newAccount->getCreatorRef()}\n"; + echo "- State: {$newAccount->state}\n"; + echo "- API Keys: " . count($newAccount->getApiKeys()) . "\n"; + + $accountId = $newAccount->id; + +} catch (Exception $e) { + echo "Failed to create account: " . $e->getMessage() . "\n"; + exit; +} + +// List all child accounts +echo "\n--- All Child Accounts ---\n"; +$childAccounts = $client->accounts->all(); + +foreach ($childAccounts as $account) { + echo "Account {$account->id}: {$account->email} ({$account->state})\n"; + + if ($account->getCreatorRef()) { + echo " Creator Ref: {$account->getCreatorRef()}\n"; + } + + $tags = $account->getTags(); + if (!empty($tags)) { + echo " Tags: " . json_encode($tags) . "\n"; + } +} + +// Update the account we just created +echo "\n--- Updating Account ---\n"; +try { + $updatedAccount = $client->accounts->update($accountId, [ + 'Tags' => [ + 'customerType' => 'enterprise', // Upgrade customer + 'region' => 'us-west', + 'lastUpdated' => date('Y-m-d H:i:s'), + ], + ]); + + echo "Updated account tags\n"; + echo "New tags: " . json_encode($updatedAccount->getTags()) . "\n"; + +} catch (Exception $e) { + echo "Failed to update account: " . $e->getMessage() . "\n"; +} + +// Demonstrate account management operations +echo "\n--- Account Management Operations ---\n"; + +// Add additional tags +try { + $client->accounts->addTags($accountId, [ + 'priority' => 'high', + 'support' => '24/7', + ]); + echo "Added additional tags\n"; +} catch (Exception $e) { + echo "Failed to add tags: " . $e->getMessage() . "\n"; +} + +// Generate new API keys +try { + $accountWithNewKeys = $client->accounts->generateApiKeys($accountId, ['staging', 'testing']); + echo "Generated new API keys: staging, testing\n"; + echo "Total API keys: " . count($accountWithNewKeys->getApiKeys()) . "\n"; +} catch (Exception $e) { + echo "Failed to generate API keys: " . $e->getMessage() . "\n"; +} + +// Suspend and reactivate account (be careful with this!) +echo "\n--- Suspend/Activate Demo ---\n"; +try { + // Suspend account + $suspendedAccount = $client->accounts->suspend($accountId); + echo "Account suspended: {$suspendedAccount->state}\n"; + + // Reactivate account + $activeAccount = $client->accounts->activate($accountId); + echo "Account reactivated: {$activeAccount->state}\n"; + +} catch (Exception $e) { + echo "Failed to suspend/activate account: " . $e->getMessage() . "\n"; +} + +// Retrieve specific account details +echo "\n--- Account Details ---\n"; +try { + $accountDetails = $client->accounts->retrieve($accountId); + + echo "Account Details:\n"; + echo "- ID: {$accountDetails->id}\n"; + echo "- Email: {$accountDetails->email}\n"; + echo "- State: {$accountDetails->state}\n"; + echo "- Active: " . ($accountDetails->isActive() ? 'Yes' : 'No') . "\n"; + echo "- Credits: {$accountDetails->credits}\n"; + echo "- Total Prints: {$accountDetails->totalPrints}\n"; + echo "- Computers: {$accountDetails->numComputers}\n"; + echo "- Can Create Sub-Accounts: " . ($accountDetails->canCreateSubAccounts() ? 'Yes' : 'No') . "\n"; + + if ($accountDetails->hasChildAccounts()) { + echo "- Child Accounts: " . count($accountDetails->getChildAccounts()) . "\n"; + } + +} catch (Exception $e) { + echo "Failed to retrieve account: " . $e->getMessage() . "\n"; +} + +// Download detailed account data +echo "\n--- Download Account Data ---\n"; +try { + $detailedAccounts = $client->accounts->download(); + echo "Downloaded detailed data for " . $detailedAccounts->count() . " accounts\n"; +} catch (Exception $e) { + echo "Failed to download account data: " . $e->getMessage() . "\n"; +} + +// Clean up - delete the test account (optional) +$deleteAccount = false; // Set to true to actually delete + +if ($deleteAccount) { + echo "\n--- Cleanup ---\n"; + try { + $client->accounts->delete($accountId); + echo "Deleted test account\n"; + } catch (Exception $e) { + echo "Failed to delete account: " . $e->getMessage() . "\n"; + } +} + +echo "\n--- Example Complete ---\n"; +echo "Account management features are now available!\n"; \ No newline at end of file diff --git a/src/Api/PrintNode/PrintNodeClient.php b/src/Api/PrintNode/PrintNodeClient.php index 9eedbf4..6c2daf4 100644 --- a/src/Api/PrintNode/PrintNodeClient.php +++ b/src/Api/PrintNode/PrintNodeClient.php @@ -9,6 +9,7 @@ /** * Client used to send requests to PrintNode's API. * + * @property-read \Rawilk\Printing\Api\PrintNode\Service\AccountService $accounts * @property-read \Rawilk\Printing\Api\PrintNode\Service\ComputerService $computers * @property-read \Rawilk\Printing\Api\PrintNode\Service\PrinterService $printers * @property-read \Rawilk\Printing\Api\PrintNode\Service\PrintJobService $printJobs diff --git a/src/Api/PrintNode/Resources/Account.php b/src/Api/PrintNode/Resources/Account.php new file mode 100644 index 0000000..e5fa8bf --- /dev/null +++ b/src/Api/PrintNode/Resources/Account.php @@ -0,0 +1,103 @@ +_values['state'] === 'active'; + } + + /** + * Indicates if the account can create sub-accounts (integrator account). + */ + public function canCreateSubAccounts(): bool + { + return $this->_values['canCreateSubAccounts'] ?? false; + } + + /** + * Get the child accounts for this account. + */ + public function getChildAccounts(): array + { + return $this->_values['childAccounts'] ?? []; + } + + /** + * Check if this account has any child accounts. + */ + public function hasChildAccounts(): bool + { + return !empty($this->getChildAccounts()); + } + + /** + * Get the creator reference for this account. + */ + public function getCreatorRef(): ?string + { + return $this->_values['creatorRef'] ?? null; + } + + /** + * Get the tags associated with this account. + */ + public function getTags(): array + { + return $this->_values['Tags'] ?? []; + } + + /** + * Get the API keys associated with this account. + */ + public function getApiKeys(): array + { + return $this->_values['ApiKeys'] ?? []; + } +} \ No newline at end of file diff --git a/src/Api/PrintNode/Service/AccountService.php b/src/Api/PrintNode/Service/AccountService.php new file mode 100644 index 0000000..2e9364c --- /dev/null +++ b/src/Api/PrintNode/Service/AccountService.php @@ -0,0 +1,140 @@ + '-', + 'lastname' => '-', + ], $params['Account'] ?? $params); + + // Structure the request properly for PrintNode API + $requestData = [ + 'Account' => $accountData, + ]; + + // Add optional fields if provided + if (isset($params['ApiKeys'])) { + $requestData['ApiKeys'] = $params['ApiKeys']; + } + + if (isset($params['Tags'])) { + $requestData['Tags'] = $params['Tags']; + } + + return $this->request('post', '/account', $requestData, $opts, Account::class); + } + + /** + * Retrieve a specific account by ID. + */ + public function retrieve(int $id, null|array|RequestOptions $opts = null): Account + { + return $this->request('get', $this->buildPath('/account/%s', $id), opts: $opts, expectedResource: Account::class); + } + + /** + * Update an existing account. + * + * @param int $id The account ID to update + * @param array $params Update parameters (same structure as create) + */ + public function update(int $id, array $params, null|array|RequestOptions $opts = null): Account + { + // Structure the request properly for PrintNode API + $requestData = []; + + if (isset($params['Account'])) { + $requestData['Account'] = $params['Account']; + } + + if (isset($params['ApiKeys'])) { + $requestData['ApiKeys'] = $params['ApiKeys']; + } + + if (isset($params['Tags'])) { + $requestData['Tags'] = $params['Tags']; + } + + return $this->request('patch', $this->buildPath('/account/%s', $id), $requestData, $opts, Account::class); + } + + /** + * Delete an account. + */ + public function delete(int $id, null|array|RequestOptions $opts = null): Account + { + return $this->request('delete', $this->buildPath('/account/%s', $id), opts: $opts, expectedResource: Account::class); + } + + /** + * List all child accounts for the current integrator account. + */ + public function all(null|array|RequestOptions $opts = null): Collection + { + return $this->requestCollection('get', '/account', opts: $opts, expectedResource: Account::class); + } + + /** + * Download child account data. + * This method retrieves detailed information about child accounts. + */ + public function download(null|array|RequestOptions $opts = null): Collection + { + return $this->requestCollection('get', '/download/accounts', opts: $opts, expectedResource: Account::class); + } + + /** + * Suspend an account. + */ + public function suspend(int $id, null|array|RequestOptions $opts = null): Account + { + return $this->request('patch', $this->buildPath('/account/%s', $id), ['Account' => ['state' => 'suspended']], $opts, Account::class); + } + + /** + * Activate a suspended account. + */ + public function activate(int $id, null|array|RequestOptions $opts = null): Account + { + return $this->request('patch', $this->buildPath('/account/%s', $id), ['Account' => ['state' => 'active']], $opts, Account::class); + } + + /** + * Add tags to an account. + */ + public function addTags(int $id, array $tags, null|array|RequestOptions $opts = null): Account + { + return $this->request('patch', $this->buildPath('/account/%s', $id), ['Tags' => $tags], $opts, Account::class); + } + + /** + * Generate new API keys for an account. + */ + public function generateApiKeys(int $id, array $keyNames, null|array|RequestOptions $opts = null): Account + { + return $this->request('patch', $this->buildPath('/account/%s', $id), ['ApiKeys' => $keyNames], $opts, Account::class); + } +} \ No newline at end of file diff --git a/src/Api/PrintNode/Service/ServiceFactory.php b/src/Api/PrintNode/Service/ServiceFactory.php index 3973a30..b9660e8 100644 --- a/src/Api/PrintNode/Service/ServiceFactory.php +++ b/src/Api/PrintNode/Service/ServiceFactory.php @@ -13,6 +13,7 @@ * * @internal * + * @property-read \Rawilk\Printing\Api\PrintNode\Service\AccountService $accounts * @property-read \Rawilk\Printing\Api\PrintNode\Service\ComputerService $computers * @property-read \Rawilk\Printing\Api\PrintNode\Service\PrinterService $printers * @property-read \Rawilk\Printing\Api\PrintNode\Service\PrintJobService $printJobs @@ -23,6 +24,7 @@ class ServiceFactory protected array $services = []; private static array $classMap = [ + 'accounts' => AccountService::class, 'computers' => ComputerService::class, 'printers' => PrinterService::class, 'printJobs' => PrintJobService::class, diff --git a/tests/Feature/Api/PrintNode/Fixtures/responses/account.json b/tests/Feature/Api/PrintNode/Fixtures/responses/account.json new file mode 100644 index 0000000..dd88abd --- /dev/null +++ b/tests/Feature/Api/PrintNode/Fixtures/responses/account.json @@ -0,0 +1,33 @@ +{ + "id": 12345, + "firstname": "-", + "lastname": "-", + "email": "customer@example.com", + "canCreateSubAccounts": false, + "creatorEmail": "integrator@example.com", + "creatorRef": "customer-ref-001", + "childAccounts": [], + "credits": 1000, + "numComputers": 1, + "totalPrints": 50, + "versions": [], + "connected": [], + "Tags": { + "customerType": "premium", + "region": "us-west" + }, + "ApiKeys": [ + { + "name": "development", + "key": "dev-api-key-123" + }, + { + "name": "production", + "key": "prod-api-key-456" + } + ], + "state": "active", + "permissions": [ + "Restricted" + ] +} \ No newline at end of file diff --git a/tests/Feature/Api/PrintNode/Fixtures/responses/integrator_whoami.json b/tests/Feature/Api/PrintNode/Fixtures/responses/integrator_whoami.json new file mode 100644 index 0000000..89e9e05 --- /dev/null +++ b/tests/Feature/Api/PrintNode/Fixtures/responses/integrator_whoami.json @@ -0,0 +1,42 @@ +{ + "id": 433, + "firstname": "Peter", + "lastname": "Tuthill", + "email": "peter@omlet.co.uk", + "canCreateSubAccounts": true, + "creatorEmail": null, + "creatorRef": null, + "childAccounts": [ + { + "id": 12345, + "email": "customer1@example.com", + "creatorRef": "customer-ref-001", + "state": "active" + }, + { + "id": 12346, + "email": "customer2@example.com", + "creatorRef": "customer-ref-002", + "state": "active" + } + ], + "credits": 10134, + "numComputers": 3, + "totalPrints": 110, + "versions": [], + "connected": [], + "Tags": { + "accountType": "integrator", + "tier": "enterprise" + }, + "ApiKeys": [ + { + "name": "main", + "key": "integrator-api-key-main" + } + ], + "state": "active", + "permissions": [ + "Unrestricted" + ] +} \ No newline at end of file diff --git a/tests/Feature/Api/PrintNode/Service/AccountServiceTest.php b/tests/Feature/Api/PrintNode/Service/AccountServiceTest.php new file mode 100644 index 0000000..52fb69e --- /dev/null +++ b/tests/Feature/Api/PrintNode/Service/AccountServiceTest.php @@ -0,0 +1,170 @@ +client = new PrintNodeClient; + $this->service = new AccountService($this->client); +}); + +it('can create a new child account', function () { + $params = [ + 'Account' => [ + 'email' => 'test@example.com', + 'password' => 'password123', + 'creatorRef' => 'test-ref-001', + ], + 'ApiKeys' => ['development', 'production'], + 'Tags' => [ + 'customerType' => 'premium', + 'region' => 'us-west', + ], + ]; + + $response = $this->service->create($params); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can retrieve an account by id', function () { + $accountId = 123; + + $response = $this->service->retrieve($accountId); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can update an existing account', function () { + $accountId = 123; + $params = [ + 'Account' => [ + 'email' => 'updated@example.com', + ], + 'Tags' => [ + 'customerType' => 'enterprise', + ], + ]; + + $response = $this->service->update($accountId, $params); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can delete an account', function () { + $accountId = 123; + + $response = $this->service->delete($accountId); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can list all child accounts', function () { + $response = $this->service->all(); + + expect($response)->toBeInstanceOf(\Illuminate\Support\Collection::class); +}); + +it('can download child account data', function () { + $response = $this->service->download(); + + expect($response)->toBeInstanceOf(\Illuminate\Support\Collection::class); +}); + +it('can suspend an account', function () { + $accountId = 123; + + $response = $this->service->suspend($accountId); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can activate a suspended account', function () { + $accountId = 123; + + $response = $this->service->activate($accountId); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can add tags to an account', function () { + $accountId = 123; + $tags = [ + 'newTag' => 'newValue', + 'anotherTag' => 'anotherValue', + ]; + + $response = $this->service->addTags($accountId, $tags); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('can generate new API keys for an account', function () { + $accountId = 123; + $keyNames = ['staging', 'testing']; + + $response = $this->service->generateApiKeys($accountId, $keyNames); + + expect($response)->toBeInstanceOf(Account::class); +}); + +it('sets default values for deprecated firstname and lastname fields', function () { + $params = [ + 'email' => 'test@example.com', + 'password' => 'password123', + ]; + + // Mock the request to verify the parameters + $this->client->shouldReceive('request') + ->once() + ->with( + 'post', + '/account', + [ + 'Account' => [ + 'firstname' => '-', + 'lastname' => '-', + 'email' => 'test@example.com', + 'password' => 'password123', + ] + ], + null, + Account::class + ) + ->andReturn(new Account); + + $this->service->create($params); +}); + +it('preserves custom firstname and lastname if provided', function () { + $params = [ + 'Account' => [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'test@example.com', + 'password' => 'password123', + ] + ]; + + // Mock the request to verify the parameters + $this->client->shouldReceive('request') + ->once() + ->with( + 'post', + '/account', + [ + 'Account' => [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'test@example.com', + 'password' => 'password123', + ] + ], + null, + Account::class + ) + ->andReturn(new Account); + + $this->service->create($params); +}); \ No newline at end of file diff --git a/tests/Unit/Api/PrintNode/Resources/AccountTest.php b/tests/Unit/Api/PrintNode/Resources/AccountTest.php new file mode 100644 index 0000000..9bf30f6 --- /dev/null +++ b/tests/Unit/Api/PrintNode/Resources/AccountTest.php @@ -0,0 +1,139 @@ +accountData = [ + 'id' => 12345, + 'firstname' => '-', + 'lastname' => '-', + 'email' => 'customer@example.com', + 'canCreateSubAccounts' => false, + 'creatorEmail' => 'integrator@example.com', + 'creatorRef' => 'customer-ref-001', + 'childAccounts' => [], + 'credits' => 1000, + 'numComputers' => 1, + 'totalPrints' => 50, + 'versions' => [], + 'connected' => [], + 'Tags' => [ + 'customerType' => 'premium', + 'region' => 'us-west', + ], + 'ApiKeys' => [ + [ + 'name' => 'development', + 'key' => 'dev-api-key-123', + ], + [ + 'name' => 'production', + 'key' => 'prod-api-key-456', + ], + ], + 'state' => 'active', + 'permissions' => ['Restricted'], + ]; + + $this->account = new Account(); + $this->account->refreshFrom($this->accountData); +}); + +it('returns correct class URL', function () { + expect(Account::classUrl())->toBe('/account'); +}); + +it('returns correct resource URL without ID', function () { + expect(Account::resourceUrl())->toBe('/account'); +}); + +it('returns correct resource URL with ID', function () { + expect(Account::resourceUrl(123))->toBe('/account/123'); +}); + +it('correctly identifies active account', function () { + expect($this->account->isActive())->toBeTrue(); +}); + +it('correctly identifies inactive account', function () { + $this->account->refreshFrom(['state' => 'suspended']); + expect($this->account->isActive())->toBeFalse(); +}); + +it('correctly identifies if account can create sub-accounts', function () { + expect($this->account->canCreateSubAccounts())->toBeFalse(); + + $this->account->refreshFrom(['canCreateSubAccounts' => true]); + expect($this->account->canCreateSubAccounts())->toBeTrue(); +}); + +it('returns child accounts', function () { + expect($this->account->getChildAccounts())->toBe([]); + + $childAccounts = [ + ['id' => 123, 'email' => 'child@example.com'], + ['id' => 124, 'email' => 'child2@example.com'], + ]; + $this->account->refreshFrom(['childAccounts' => $childAccounts]); + expect($this->account->getChildAccounts())->toBe($childAccounts); +}); + +it('correctly identifies if account has child accounts', function () { + expect($this->account->hasChildAccounts())->toBeFalse(); + + $this->account->refreshFrom(['childAccounts' => [['id' => 123]]]); + expect($this->account->hasChildAccounts())->toBeTrue(); +}); + +it('returns creator reference', function () { + expect($this->account->getCreatorRef())->toBe('customer-ref-001'); + + $this->account->refreshFrom(['creatorRef' => null]); + expect($this->account->getCreatorRef())->toBeNull(); +}); + +it('returns tags', function () { + expect($this->account->getTags())->toBe([ + 'customerType' => 'premium', + 'region' => 'us-west', + ]); + + $this->account->refreshFrom(['Tags' => []]); + expect($this->account->getTags())->toBe([]); +}); + +it('returns API keys', function () { + $expectedApiKeys = [ + [ + 'name' => 'development', + 'key' => 'dev-api-key-123', + ], + [ + 'name' => 'production', + 'key' => 'prod-api-key-456', + ], + ]; + + expect($this->account->getApiKeys())->toBe($expectedApiKeys); + + $this->account->refreshFrom(['ApiKeys' => []]); + expect($this->account->getApiKeys())->toBe([]); +}); + +it('handles missing optional properties gracefully', function () { + $minimalData = [ + 'id' => 123, + 'email' => 'test@example.com', + 'state' => 'active', + ]; + + $account = new Account(); + $account->refreshFrom($minimalData); + + expect($account->canCreateSubAccounts())->toBeFalse(); + expect($account->getChildAccounts())->toBe([]); + expect($account->hasChildAccounts())->toBeFalse(); + expect($account->getCreatorRef())->toBeNull(); + expect($account->getTags())->toBe([]); + expect($account->getApiKeys())->toBe([]); +}); \ No newline at end of file