Skip to content
Merged
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
156 changes: 149 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ TIMATIC_API_TOKEN=your-api-token-here
The SDK connector is automatically registered in Laravel's service container, making it easy to inject into your controllers, commands, and other classes:

```php
use Timatic\SDK\TimaticConnector;
use Timatic\SDK\Requests\BudgetType\GetBudgetTypeCollection;
use Timatic\TimaticConnector;
use Timatic\Requests\BudgetType\GetBudgetTypeCollection;

class BudgetController extends Controller
{
Expand All @@ -66,13 +66,13 @@ class BudgetController extends Controller

public function store(Request $request)
{
$budget = new \Timatic\SDK\Dto\Budget([
$budget = new \Timatic\Dto\Budget([
'title' => $request->input('title'),
'totalPrice' => $request->input('total_price'),
]);

$created = $this->timatic
->send(new \Timatic\SDK\Requests\Budget\PostBudgets($budget))
->send(new \Timatic\Requests\Budget\PostBudgets($budget))
->dtoOrFail();

return redirect()->route('budgets.show', $created->id);
Expand All @@ -83,7 +83,7 @@ class BudgetController extends Controller
**In Console Commands:**

```php
use Timatic\SDK\TimaticConnector;
use Timatic\TimaticConnector;

class SyncBudgetsCommand extends Command
{
Expand All @@ -100,13 +100,155 @@ class SyncBudgetsCommand extends Command
}
```

### Testing

When testing code that uses the Timatic SDK, you can mock the connector and its responses using factories. The SDK includes factory classes for all DTOs that make it easy to generate test data.

Here's an example of testing the `BudgetController` from the example above:

```php
use Timatic\TimaticConnector;
use Timatic\Dto\Budget;
use Timatic\Dto\BudgetType;
use Timatic\Requests\Budget\GetBudgetsRequest;
use Timatic\Requests\BudgetType\GetBudgetTypesRequest;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;

test('it displays budgets and budget types', function () {
// Generate test data using factories
$budget = Budget::factory()->state(['id' => '1'])->make();
$budgetType = BudgetType::factory()->state(['id' => '1'])->make();

// Create mock responses using factory-generated data
$mockClient = new MockClient([
GetBudgetsRequest::class => MockResponse::make([
'data' => [$budget->toJsonApi()],
], 200),
GetBudgetTypesRequest::class => MockResponse::make([
'data' => [$budgetType->toJsonApi()],
], 200),
]);

// Bind mock to container
$connector = new TimaticConnector();
$connector->withMockClient($mockClient);
$this->app->instance(TimaticConnector::class, $connector);

// Make request
$response = $this->get(route('budgets.index'));

// Assert
$response->assertOk();
$response->assertViewHas('budgets');
$response->assertViewHas('budgetTypes');
});

test('it creates a new budget', function () {
// Generate test data with specific attributes
$budget = Budget::factory()->state([
'id' => '2',
'title' => 'New Budget',
'totalPrice' => '5000.00',
])->make();

$mockClient = new MockClient([
PostBudgetsRequest::class => MockResponse::make([
'data' => $budget->toJsonApi(),
], 201),
]);

$connector = new TimaticConnector();
$connector->withMockClient($mockClient);
$this->app->instance(TimaticConnector::class, $connector);

$response = $this->post(route('budgets.store'), [
'title' => 'New Budget',
'total_price' => 5000.00,
]);

$response->assertRedirect(route('budgets.show', '2'));
});

test('it sends a POST request to create a budget using the SDK', function () {
$budgetToCreate = Budget::factory()->state([
'title' => 'New Budget',
'totalPrice' => '5000.00',
'customerId' => 'customer-123',
])->make();

$createdBudget = Budget::factory()->state([
'id' => 'created-456',
'title' => 'New Budget',
'totalPrice' => '5000.00',
'customerId' => 'customer-123',
])->make();

$mockClient = new MockClient([
PostBudgetsRequest::class => MockResponse::make([
'data' => $createdBudget->toJsonApi(),
], 201),
]);

$connector = new TimaticConnector();
$connector->withMockClient($mockClient);

$response = $connector->send(new PostBudgetsRequest($budgetToCreate));

// Assert the request body was sent correctly
$mockClient->assertSent(function (\Saloon\Http\Request $request) {
$body = $request->body()->all();

return $body['data']['attributes']['title'] === 'New Budget'
&& $body['data']['attributes']['totalPrice'] === '5000.00'
&& $body['data']['attributes']['customerId'] === 'customer-123';
});

// Assert response
expect($response->status())->toBe(201);

$dto = $response->dto();
expect($dto)
->toBeInstanceOf(Budget::class)
->id->toBe('created-456')
->title->toBe('New Budget')
->totalPrice->toBe('5000.00');
});
```

#### Factory Methods

Every DTO in the SDK has a corresponding factory class with the following methods:

```php
// Create a single model with random data
$budget = Budget::factory()->make();

// Create multiple models with unique UUID IDs
$budgets = Budget::factory()->withId()->count(3)->make(); // Returns Collection

// Override specific attributes
$budget = Budget::factory()->state([
'title' => 'Q1 Budget',
'totalPrice' => '10000.00',
])->make();

// Chain state calls for complex scenarios
$budget = Budget::factory()
->state(['customerId' => $customerId])
->state(['budgetTypeId' => $budgetTypeId])
->make();
```

For more information on mocking Saloon requests, see the [Saloon Mocking Documentation](https://docs.saloon.dev/testing/faking-responses).

### Pagination

The SDK supports automatic pagination for all collection endpoints using Saloon's pagination plugin:

```php
use Timatic\SDK\TimaticConnector;
use Timatic\SDK\Requests\Budget\GetBudgets;
use Timatic\TimaticConnector;
use Timatic\Requests\Budget\GetBudgets;

class BudgetController extends Controller
{
Expand Down
12 changes: 7 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"license": "Elastic-2.0",
"autoload": {
"psr-4": {
"Timatic\\SDK\\": "src/",
"Timatic\\SDK\\Generator\\": "generator/"
"Timatic\\": "src/",
"Timatic\\Generator\\": "generator/",
"Timatic\\Factories\\": "factories/"
}
},
"autoload-dev": {
"psr-4": {
"Timatic\\SDK\\Tests\\": "tests/"
"Timatic\\Tests\\": "tests/"
}
},
"require": {
Expand All @@ -21,6 +22,7 @@
"saloonphp/pagination-plugin": "^2.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/boost": "^1.8",
"phpunit/phpunit": "^10.0|^11.0",
"pestphp/pest": "^2.0|^3.0",
Expand Down Expand Up @@ -49,10 +51,10 @@
"extra": {
"laravel": {
"providers": [
"Timatic\\SDK\\Providers\\TimaticServiceProvider"
"Timatic\\Providers\\TimaticServiceProvider"
],
"aliases": {
"Timatic": "Timatic\\SDK\\Facades\\Timatic"
"Timatic": "Timatic\\Facades\\Timatic"
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions example.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require_once __DIR__.'/vendor/autoload.php';

use Timatic\SDK\TimaticConnector;
use Timatic\TimaticConnector;

// Initialize the Timatic SDK
$timatic = new TimaticConnector;
Expand Down Expand Up @@ -42,7 +42,7 @@
// Example 5: Create a new entry (commented out to prevent accidental execution)

echo "Creating a new entry...\n";
$response = $timatic->entry()->postEntries(new \Timatic\SDK\Dto\Entry([
$response = $timatic->entry()->postEntries(new \Timatic\Dto\Entry([
'user_id' => 1,
'customer_id' => 1,
'date' => date('Y-m-d'),
Expand Down
32 changes: 32 additions & 0 deletions factories/ApproveFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

// auto-generated

namespace Timatic\Factories;

use Carbon\Carbon;
use Timatic\Dto\Approve;

class ApproveFactory extends Factory
{
protected function definition(): array
{
return [
'entryId' => $this->faker->uuid(),
'overtimeTypeId' => $this->faker->uuid(),
'startedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'endedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'percentages' => $this->faker->word(),
'approvedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'approvedByUserId' => $this->faker->uuid(),
'exportedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'createdAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'updatedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
];
}

protected function modelClass(): string
{
return Approve::class;
}
}
38 changes: 38 additions & 0 deletions factories/BudgetFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

// auto-generated

namespace Timatic\Factories;

use Carbon\Carbon;
use Timatic\Dto\Budget;

class BudgetFactory extends Factory
{
protected function definition(): array
{
return [
'budgetTypeId' => $this->faker->uuid(),
'customerId' => $this->faker->uuid(),
'showToCustomer' => $this->faker->boolean(),
'changeId' => $this->faker->uuid(),
'contractId' => $this->faker->uuid(),
'title' => $this->faker->sentence(),
'description' => $this->faker->sentence(),
'totalPrice' => $this->faker->word(),
'startedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'endedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'initialMinutes' => $this->faker->numberBetween(15, 480),
'isArchived' => $this->faker->boolean(),
'renewalFrequency' => $this->faker->word(),
'createdAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'updatedAt' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'supervisorUserId' => $this->faker->uuid(),
];
}

protected function modelClass(): string
{
return Budget::class;
}
}
27 changes: 27 additions & 0 deletions factories/BudgetTimeSpentTotalFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

// auto-generated

namespace Timatic\Factories;

use Carbon\Carbon;
use Timatic\Dto\BudgetTimeSpentTotal;

class BudgetTimeSpentTotalFactory extends Factory
{
protected function definition(): array
{
return [
'start' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'end' => Carbon::now()->subDays($this->faker->numberBetween(0, 365)),
'remainingMinutes' => $this->faker->numberBetween(15, 480),
'periodUnit' => $this->faker->word(),
'periodValue' => $this->faker->numberBetween(1, 100),
];
}

protected function modelClass(): string
{
return BudgetTimeSpentTotal::class;
}
}
30 changes: 30 additions & 0 deletions factories/BudgetTypeFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

// auto-generated

namespace Timatic\Factories;

use Timatic\Dto\BudgetType;

class BudgetTypeFactory extends Factory
{
protected function definition(): array
{
return [
'title' => $this->faker->sentence(),
'isArchived' => $this->faker->boolean(),
'hasChangeTicket' => $this->faker->boolean(),
'renewalFrequencies' => $this->faker->word(),
'hasSupervisor' => $this->faker->boolean(),
'hasContractId' => $this->faker->uuid(),
'hasTotalPrice' => $this->faker->boolean(),
'ticketIsRequired' => $this->faker->boolean(),
'defaultTitle' => $this->faker->sentence(),
];
}

protected function modelClass(): string
{
return BudgetType::class;
}
}
Loading