Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fetch endpoints with Saloon #1

Merged
merged 7 commits into from
Jan 31, 2025
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
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
],
"require": {
"php": "^8.3",
"spatie/laravel-package-tools": "^1.16",
"illuminate/contracts": "^11.0"
"illuminate/contracts": "^11.0",
"saloonphp/laravel-plugin": "^3.0",
"spatie/laravel-package-tools": "^1.16"
},
"require-dev": {
"laravel/pint": "^1.14",
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ includes:
- phpstan-baseline.neon

parameters:
level: 5
level: 6
paths:
- src
- config
Expand Down
58 changes: 39 additions & 19 deletions src/FloatClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,50 @@

namespace Spatie\FloatSdk;

use GuzzleHttp\Client;
use Spatie\FloatSdk\Tests\Fake\FakeFloatClient;
use Saloon\Http\Auth\TokenAuthenticator;
use Saloon\Http\Connector;
use Spatie\FloatSdk\Groups\ProjectsGroup;
use Spatie\FloatSdk\Groups\TasksGroup;
use Spatie\FloatSdk\Groups\UsersGroup;

class FloatClient
class FloatClient extends Connector
{
protected Client $client;

public function __construct(
private readonly string $apiKey,
private readonly string $userAgent,
) {
$this->client = new Client([
'base_uri' => 'https://api.float.com/v3',
'headers' => [
'Authorization' => 'Bearer '.$this->apiKey,
'Accept' => 'application/json',
'User-Agent' => $this->userAgent,
],
]);
private string $apiKey,
private string $userAgent,
) {}

public function resolveBaseUrl(): string
{
return 'https://api.float.com/v3';
}

protected function defaultHeaders(): array
{
return [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => $this->userAgent,
];
}

protected function defaultAuth(): TokenAuthenticator
{
return new TokenAuthenticator($this->apiKey);
}

public function users(): UsersGroup
{
return new UsersGroup($this);
}

public function projects(): ProjectsGroup
{
return new ProjectsGroup($this);
}

/** @internal for testing purposes only */
public static function fake(): void
public function tasks(): TasksGroup
{
app()->instance(self::class, new FakeFloatClient);
return new TasksGroup($this);
}
}
15 changes: 15 additions & 0 deletions src/Groups/ProjectsGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\FloatSdk\Groups;

use Saloon\Http\BaseResource;
use Saloon\Http\Response;
use Spatie\FloatSdk\Requests\GetProjects;

class ProjectsGroup extends BaseResource
{
public function all(): Response
{
return $this->connector->send(new GetProjects);
}
}
15 changes: 15 additions & 0 deletions src/Groups/TasksGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\FloatSdk\Groups;

use Saloon\Http\BaseResource;
use Saloon\Http\Response;
use Spatie\FloatSdk\Requests\GetTasks;

class TasksGroup extends BaseResource
{
public function all(): Response
{
return $this->connector->send(new GetTasks);
}
}
15 changes: 15 additions & 0 deletions src/Groups/UsersGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\FloatSdk\Groups;

use Saloon\Http\BaseResource;
use Saloon\Http\Response;
use Spatie\FloatSdk\Requests\GetUsers;

class UsersGroup extends BaseResource
{
public function all(): Response
{
return $this->connector->send(new GetUsers);
}
}
26 changes: 26 additions & 0 deletions src/Requests/GetProjects.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Spatie\FloatSdk\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\FloatSdk\Resources\ProjectResource;

class GetProjects extends Request
{
protected Method $method = Method::GET;

public function resolveEndpoint(): string
{
return '/projects';
}

/** @return array<ProjectResource> */
public function createDtoFromResponse(Response $response): array
{
return array_map(function (array $object) {
return ProjectResource::createFromResponse($object);
}, $response->json());
}
}
26 changes: 26 additions & 0 deletions src/Requests/GetTasks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Spatie\FloatSdk\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\FloatSdk\Resources\TaskResource;

class GetTasks extends Request
{
protected Method $method = Method::GET;

public function resolveEndpoint(): string
{
return '/project-tasks';
}

/** @return array<TaskResource> */
public function createDtoFromResponse(Response $response): array
{
return array_map(function (array $object) {
return TaskResource::createFromResponse($object);
}, $response->json());
}
}
26 changes: 26 additions & 0 deletions src/Requests/GetUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Spatie\FloatSdk\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Spatie\FloatSdk\Resources\UserResource;

class GetUsers extends Request
{
protected Method $method = Method::GET;

public function resolveEndpoint(): string
{
return '/people';
}

/** @return array<UserResource> */
public function createDtoFromResponse(Response $response): array
{
return array_map(function (array $object) {
return UserResource::createFromResponse($object);
}, $response->json());
}
}
24 changes: 24 additions & 0 deletions src/Resources/ProjectResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Spatie\FloatSdk\Resources;

class ProjectResource
{
public function __construct(
public int $id,
public string $name,
public string $code,
public string $clientId,
) {}

/** @param array<mixed> $response */
public static function createFromResponse(array $response): self
{
return new self(
id: $response['project_id'],
name: $response['name'],
code: $response['code'],
clientId: $response['client_id'],
);
}
}
22 changes: 22 additions & 0 deletions src/Resources/TaskResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Spatie\FloatSdk\Resources;

class TaskResource
{
public function __construct(
public int $id,
public string $name,
public string $projectId,
) {}

/** @param array<mixed> $response */
public static function createFromResponse(array $response): self
{
return new self(
id: $response['task_meta_id'],
name: $response['task_name'],
projectId: $response['project_id'],
);
}
}
26 changes: 26 additions & 0 deletions src/Resources/UserResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Spatie\FloatSdk\Resources;

class UserResource
{
public function __construct(
public int $id,
public string $name,
public string $email,
public string $jobTitle,
public bool $active
) {}

/** @param array<mixed> $response */
public static function createFromResponse(array $response): self
{
return new self(
id: $response['people_id'],
name: $response['name'],
email: $response['email'],
jobTitle: $response['job_title'],
active: $response['active'],
);
}
}
14 changes: 0 additions & 14 deletions tests/Fake/FakeFloatClient.php

This file was deleted.

14 changes: 0 additions & 14 deletions tests/FloatServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Spatie\FloatSdk\FloatClient;
use Spatie\FloatSdk\FloatServiceProvider;
use Spatie\FloatSdk\Tests\Fake\FakeFloatClient;

beforeEach(function () {
$this->provider = new FloatServiceProvider($this->app);
Expand Down Expand Up @@ -44,16 +43,3 @@

app(FloatClient::class);
})->throws('Float requires a User-Agent header in the format: `YourAppName (your-email@example.com)`. Please set the `FLOAT_USER_AGENT` environment variable accordingly.');

it('can create a fake', function () {
config()->set('float-sdk', [
'api_token' => 'fake-token',
'user_agent' => null,
]);

FloatClient::fake();

$client = app(FloatClient::class);

expect($client)->toBeInstanceOf(FakeFloatClient::class);
});
60 changes: 60 additions & 0 deletions tests/Requests/GetUsersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Spatie\FloatSdk\Tests\Requests;

use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
use Spatie\FloatSdk\FloatClient;
use Spatie\FloatSdk\Requests\GetUsers;

beforeEach(function () {
$people = [
['id' => 00000001, 'name' => 'Wouter', 'email' => 'wouter@spatie.com', 'job_title' => 'Project manager', 'active' => 0],
['id' => 00000002, 'name' => 'Séba', 'email' => 'sebastien@spatie.com', 'job_title' => 'Frontend developer', 'active' => 1],
['id' => 00000003, 'name' => 'Jimi', 'email' => 'jimi@spatie.com', 'job_title' => 'Digital designer', 'active' => 1],
['id' => 00000004, 'name' => 'Tim', 'email' => 'tim@spatie.com', 'job_title' => 'Backend developer', 'active' => 1],
['id' => 00000005, 'name' => 'Niels', 'email' => 'niels@spatie.com', 'job_title' => 'Backend developer', 'active' => 1],
['id' => 00000006, 'name' => 'Willem', 'email' => 'willem@spatie.com', 'job_title' => 'Frontend developer', 'active' => 0],
['id' => 00000007, 'name' => 'Sebastian', 'email' => 'sebastian@spatie.com', 'job_title' => 'Frontend developer', 'active' => 1],
['id' => 00000010, 'name' => 'Ruben', 'email' => 'ruben@spatie.com', 'job_title' => 'Backend developer', 'active' => 1],
['id' => 00000011, 'name' => 'Rias', 'email' => 'rias@spatie.com', 'job_title' => 'Backend developer', 'active' => 1],
['id' => 00000012, 'name' => 'Freek', 'email' => 'freek@spatie.com', 'job_title' => 'Backend developer', 'active' => 1],
['id' => 00000013, 'name' => 'Alex', 'email' => 'alex@spatie.com', 'job_title' => 'Backend developer', 'active' => 0],
];

$this->mockClient = new MockClient([
GetUsers::class => MockResponse::make(body: $people),
]);

$this->client = new FloatClient('fake-api-key', 'fake-user_agent');
});

it('can fetch all the users of an organisation', function () {
$response = $this->client
->withMockClient($this->mockClient)
->send(new GetUsers);

expect($response->json())
->toBeArray()
->toHaveCount(11);

$firstUser = $response->json()[0];

expect($firstUser)
->toHaveKeys(['id', 'name', 'email', 'job_title', 'active']);
});

it('can fetch the users with a specific method', function () {
$response = $this->client
->withMockClient($this->mockClient)
->users()->all();

expect($response->json())
->toBeArray()
->toHaveCount(11);

$firstUser = $response->json()[0];

expect($firstUser)
->toHaveKeys(['id', 'name', 'email', 'job_title', 'active']);
});