From 806896a3ecceab312688a82a801f3b2292042a41 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 10:46:42 +0200 Subject: [PATCH 1/4] Implemented API Request + updated docs --- README.md | 210 +++++++++- src/Data/Configs/AuthenticatedUserStatus.php | 2 - src/Data/Configs/Reaction.php | 23 -- src/Data/Streams/Post.php | 114 +++++ src/Data/Streams/Stream.php | 50 +++ src/Enums/Streams/Type.php | 14 + src/Requests/CreateAPostInAGivenStream.php | 149 +++++++ .../CreateAPostInAGivenStreamResponse.php | 37 ++ .../CreateAPostInAGivenStreamRequestTest.php | 278 +++++++++++++ .../CreateAPostInAGivenStreamRequestTest.php | 390 ++++++++++++++++++ 10 files changed, 1229 insertions(+), 38 deletions(-) delete mode 100755 src/Data/Configs/Reaction.php create mode 100644 src/Data/Streams/Post.php create mode 100644 src/Data/Streams/Stream.php create mode 100644 src/Enums/Streams/Type.php create mode 100644 src/Requests/CreateAPostInAGivenStream.php create mode 100644 src/Responses/CreateAPostInAGivenStreamResponse.php create mode 100644 tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php create mode 100644 tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php diff --git a/README.md b/README.md index 13b029e..736618b 100755 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ [![Dependency Review](https://github.com/codebar-ag/laravel-beekeeper/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/codebar-ag/laravel-beekeeper/actions/workflows/dependency-review.yml) This package was developed to give you a quick start to communicate with the -Beekeeper Api. It is used to query the most common endpoints. +Beekeeper API using token-based authentication. It provides a clean, type-safe +interface to query the most common Beekeeper endpoints including artifacts, +files, streams, and posts. ## Navigation @@ -23,7 +25,9 @@ Beekeeper Api. It is used to query the most common endpoints. * [List Artifacts](#list-artifacts) * [Upload A File](#upload-a-file) * [Create A Child To An Artifact](#create-a-child-to-an-artifact) + * [Create A Post In A Given Stream](#create-a-post-in-a-given-stream) * [DTO Showcase](#dto-showcase) + * [Available Enums](#available-enums) * [Testing](#testing) * [Changelog](#changelog) * [Contributing](#contributing) @@ -67,20 +71,30 @@ This is the contents of the published config file: env('BEEKEEPER_API_TOKEN'), + 'endpoint_prefix' => env('BEEKEEPER_ENDPOINT_PREFIX'), + 'cache_store' => env('BEEKEEPER_CACHE_STORE') ]; ``` You should finally add the following to your .env file: ```env -BEEKEEPER_CLIENT_ID=your-client-id -BEEKEEPER_CLIENT_SECRET=your-client-secret -BEEKEEPER_CACHE_STORE=beekeeper +BEEKEEPER_API_TOKEN=your-api-token +BEEKEEPER_ENDPOINT_PREFIX=codebar.us +BEEKEEPER_CACHE_STORE=file ``` ## Usage +### Authentication + +This package uses token-based authentication with the Beekeeper API. You'll need to: + +1. Obtain an API token from your Beekeeper admin panel +2. Set the `BEEKEEPER_API_TOKEN` environment variable +3. Set your Beekeeper subdomain in `BEEKEEPER_ENDPOINT_PREFIX` + ### Get the connector ```php @@ -154,6 +168,57 @@ $response = $connector->send(new CreateAChildToAnArtifact( )); ``` +### Create A Post In A Given Stream + +```php +use CodebarAg\LaravelBeekeeper\Requests\CreateAPostInAGivenStream; + +// Basic post creation +$response = $connector->send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!' +)); + +// Advanced post with all options +$fileData = [ + 'updated' => '2016-10-07T12:49:21', + 'name' => 'fair_play_rules.pdf', + 'created' => '2016-10-07T12:49:21', + 'url' => 'https://mytenant.beekeeper.io/file/665987/original/fair_play_rules.pdf', + 'userid' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'height' => 619, + 'width' => 700, + 'duration' => 315, + 'key' => 'f4fdaab0-d198-49b4-b1cc-dd85572d72f1', + 'media_type' => 'image/png', + 'usage_type' => 'attachment_image', + 'id' => 66598, + 'size' => 85 +]; + +$response = $connector->send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!', + title: 'Hello guys!', + labels: ['food', 'poll', 'events'], + sticky: true, + locked: true, + reactionsDisabled: true, + scheduledAt: '2019-08-24T14:15:22', + files: [$fileData], + photos: [$fileData], + videos: [$fileData], + media: [$fileData], + options: [ + ['text' => 'This Friday'], + ['text' => 'Monday next week'] + ], + expand: ['user', 'stream'] +)); + +$post = $response->dto(); // Returns a Post DTO +``` + ## DTO Showcase ```php @@ -183,7 +248,6 @@ CodebarAg\LaravelBeekeeper\Data\Configs\AuthenticatedUserStatus { +maxVideoSizeForAdmins: 2147483648 // int|null +maxVoiceRecordingLength: 900 // int|null +maxUsersInGroupChat: 200 // int|null - +reactions: Illuminate\Support\Collection // Collection|null +featureFlags: Illuminate\Support\Collection // Collection|null +integrations: Illuminate\Support\Collection // Collection|null +styling: Illuminate\Support\Collection // Collection|null @@ -209,13 +273,6 @@ CodebarAg\LaravelBeekeeper\Data\Configs\General { } ``` -```php -CodebarAg\LaravelBeekeeper\Data\Configs\Reaction { - +cldrShortName: "thumbs up" // string - +name: "Like" // string - +emoji: "👍" // string -} -``` ```php CodebarAg\LaravelBeekeeper\Data\Files\File { @@ -246,7 +303,134 @@ CodebarAg\LaravelBeekeeper\Data\Files\FileVersion { } ``` +```php +CodebarAg\LaravelBeekeeper\Data\Streams\Post { + +id: 2234 // int + +text: "Please indicate your preferred dates for next team event in the poll below. Thanks!" // string + +title: "Hello guys!" // string|null + +labels: Illuminate\Support\Collection // Collection + +sticky: true // bool + +likeCount: 42 // int + +streamId: 6002 // int + +digest: 1 // int + +userId: "5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17" // string + +uuid: "731b28bc-7f10-4b68-a089-fc672abc9955" // string + +commentCount: 2 // int + +reportCount: 0 // int + +source: "beekeeper" // string + +voteCount: 12 // int + +moderated: true // bool + +photo: "https://d6698txzbomp3.cloudfront.net/72e3b7d4-c6a4-47e9-8f81-7b7d10bdd84a" // string|null + +languageConfidence: 0.86 // float|null + +type: "post" // string + +metadata: "string" // string|null + +profile: "peter_smith" // string|null + +edited: true // bool + +displayNameExtension: "General Manager" // string|null + +subscribedByUser: true // bool + +reportable: true // bool + +anonymous: true // bool + +displayName: "John Smith" // string|null + +unread: true // bool + +locked: true // bool + +reactionsDisabled: true // bool + +name: "Peter Smith" // string|null + +language: "en" // string|null + +languageInformation: array // array|null + +created: Carbon\CarbonImmutable // CarbonImmutable|null + +postedByUser: true // bool + +avatar: "https://dz343oy86h947.cloudfront.net/business/neutral/normal/05.png" // string|null + +reportedByUser: true // bool + +likedByUser: true // bool + +mentions: Illuminate\Support\Collection // Collection + +mentionsDetails: array // array|null + +scheduledAt: Carbon\CarbonImmutable // CarbonImmutable|null + +status: "published" // string|null + +files: Illuminate\Support\Collection // Collection + +photos: Illuminate\Support\Collection // Collection + +videos: Illuminate\Support\Collection // Collection + +media: Illuminate\Support\Collection // Collection + +options: Illuminate\Support\Collection // Collection + +stateId: "2017-06-19T08:49:53" // string|null +} +``` + +```php +CodebarAg\LaravelBeekeeper\Data\Streams\Stream { + +id: "12345678-abcd-efgh-9012-de00edbf7b0b" // string + +tenantId: "12345" // string + +name: "General Discussion" // string + +description: "General discussion stream for all team members" // string|null + +type: CodebarAg\LaravelBeekeeper\Enums\Streams\Type // Type|null + +isPublic: true // bool + +isArchived: false // bool + +createdAt: Carbon\CarbonImmutable // CarbonImmutable|null + +updatedAt: Carbon\CarbonImmutable // CarbonImmutable|null + +createdBy: "12345678-abcd-efgh-9012-de00edbf7b0b" // string|null + +updatedBy: "12345678-abcd-efgh-9012-de00edbf7b0b" // string|null + +posts: Illuminate\Support\Collection // Collection + +subscribers: Illuminate\Support\Collection // Collection + +permissions: Illuminate\Support\Collection // Collection + +metadata: Illuminate\Support\Collection // Collection +} +``` + +## Available Enums + +The package provides several enums for type safety and better code organization: + +### Artifact Enums + +```php +use CodebarAg\LaravelBeekeeper\Enums\Artifacts\Type; +use CodebarAg\LaravelBeekeeper\Enums\Artifacts\Sort; +// Artifact types +Type::FOLDER +Type::FILE + +// Sorting options +Sort::NAME_ASC +Sort::NAME_DESC +Sort::CREATED_ASC +Sort::CREATED_DESC +``` + +### File Enums + +```php +use CodebarAg\LaravelBeekeeper\Enums\Files\Status; +use CodebarAg\LaravelBeekeeper\Enums\Files\UsageType; + +// File status +Status::PROCESSING +Status::READY +Status::ERROR + +// Usage types +UsageType::ATTACHMENT_IMAGE +UsageType::ATTACHMENT_FILE +UsageType::ATTACHMENT_VIDEO +UsageType::AVATAR +UsageType::COVER_IMAGE +UsageType::LOGO +// ... and more +``` + +### Stream Enums + +```php +use CodebarAg\LaravelBeekeeper\Enums\Streams\Type; + +// Stream types +Type::PUBLIC +Type::PRIVATE +Type::ANNOUNCEMENT +Type::DISCUSSION +Type::PROJECT +Type::DEPARTMENT +Type::TEAM +``` ## Testing diff --git a/src/Data/Configs/AuthenticatedUserStatus.php b/src/Data/Configs/AuthenticatedUserStatus.php index 1529573..95a7375 100755 --- a/src/Data/Configs/AuthenticatedUserStatus.php +++ b/src/Data/Configs/AuthenticatedUserStatus.php @@ -18,7 +18,6 @@ public static function make(array $data): self maxVideoSizeForAdmins: Arr::get($data, 'max_video_size_for_admins'), maxVoiceRecordingLength: Arr::get($data, 'max_voice_recording_length'), maxUsersInGroupChat: Arr::get($data, 'max_users_in_group_chat'), - reactions: Arr::has($data, 'reactions') ? collect(Arr::get($data, 'reactions'))->map(fn (array $reaction) => Reaction::make($reaction)) : null, featureFlags: collect(Arr::get($data, 'feature_flags')), integrations: collect(Arr::get($data, 'integrations')), styling: collect(Arr::get($data, 'styling')), @@ -36,7 +35,6 @@ public function __construct( public ?int $maxVideoSizeForAdmins, public ?int $maxVoiceRecordingLength, public ?int $maxUsersInGroupChat, - public ?Collection $reactions = null, public ?Collection $featureFlags = null, public ?Collection $integrations = null, public ?Collection $styling = null, diff --git a/src/Data/Configs/Reaction.php b/src/Data/Configs/Reaction.php deleted file mode 100755 index 9762ec9..0000000 --- a/src/Data/Configs/Reaction.php +++ /dev/null @@ -1,23 +0,0 @@ -map(fn (array $file) => File::make($file)), + photos: collect(Arr::get($data, 'photos', []))->map(fn (array $photo) => File::make($photo)), + videos: collect(Arr::get($data, 'videos', []))->map(fn (array $video) => File::make($video)), + media: collect(Arr::get($data, 'media', []))->map(fn (array $media) => File::make($media)), + options: collect(Arr::get($data, 'options', [])), + stateId: Arr::get($data, 'state_id'), + ); + } + + public function __construct( + public int $id, + public string $text, + public ?string $title, + public Collection $labels, + public bool $sticky, + public int $likeCount, + public int $streamId, + public int $digest, + public string $userId, + public string $uuid, + public int $commentCount, + public int $reportCount, + public string $source, + public int $voteCount, + public bool $moderated, + public ?string $photo, + public ?float $languageConfidence, + public string $type, + public ?string $metadata, + public ?string $profile, + public bool $edited, + public ?string $displayNameExtension, + public bool $subscribedByUser, + public bool $reportable, + public bool $anonymous, + public ?string $displayName, + public bool $unread, + public bool $locked, + public bool $reactionsDisabled, + public ?string $name, + public ?string $language, + public ?array $languageInformation, + public ?CarbonImmutable $created, + public bool $postedByUser, + public ?string $avatar, + public bool $reportedByUser, + public bool $likedByUser, + public Collection $mentions, + public ?array $mentionsDetails, + public ?CarbonImmutable $scheduledAt, + public ?string $status, + public Collection $files, + public Collection $photos, + public Collection $videos, + public Collection $media, + public Collection $options, + public ?string $stateId, + ) {} +} diff --git a/src/Data/Streams/Stream.php b/src/Data/Streams/Stream.php new file mode 100644 index 0000000..82db78a --- /dev/null +++ b/src/Data/Streams/Stream.php @@ -0,0 +1,50 @@ +map(fn (array $post) => Post::make($post)), + subscribers: collect(Arr::get($data, 'subscribers', [])), + permissions: collect(Arr::get($data, 'permissions', [])), + metadata: collect(Arr::get($data, 'metadata', [])), + ); + } + + public function __construct( + public string $id, + public string $tenantId, + public string $name, + public ?string $description, + public ?Type $type, + public bool $isPublic, + public bool $isArchived, + public ?CarbonImmutable $createdAt, + public ?CarbonImmutable $updatedAt, + public ?string $createdBy, + public ?string $updatedBy, + public Collection $posts, + public Collection $subscribers, + public Collection $permissions, + public Collection $metadata, + ) {} +} diff --git a/src/Enums/Streams/Type.php b/src/Enums/Streams/Type.php new file mode 100644 index 0000000..bb4bf61 --- /dev/null +++ b/src/Enums/Streams/Type.php @@ -0,0 +1,14 @@ +streamId.'/posts'; + } + + protected function defaultQuery(): array + { + $expand = $this->expand; + + if ($expand instanceof Collection) { + $expand = $expand->toArray(); + } + + return [ + 'expand' => implode(',', $expand), + ]; + } + + public function defaultBody(): array + { + $body = [ + 'text' => $this->text, + ]; + + if (! empty($this->title)) { + $body = Arr::add(array: $body, key: 'title', value: $this->title); + } + + $labels = $this->labels; + + if ($labels instanceof Collection) { + $labels = $labels->toArray(); + } + + if (! empty($labels)) { + $body = Arr::add(array: $body, key: 'labels', value: $labels); + } + + if ($this->sticky) { + $body = Arr::add(array: $body, key: 'sticky', value: $this->sticky); + } + + if ($this->locked) { + $body = Arr::add(array: $body, key: 'locked', value: $this->locked); + } + + if ($this->reactionsDisabled) { + $body = Arr::add(array: $body, key: 'reactions_disabled', value: $this->reactionsDisabled); + } + + if (! empty($this->scheduledAt)) { + $body = Arr::add(array: $body, key: 'scheduled_at', value: $this->scheduledAt); + } + + $files = $this->files; + + if ($files instanceof Collection) { + $files = $files->toArray(); + } + + if (! empty($files)) { + $body = Arr::add(array: $body, key: 'files', value: $files); + } + + $photos = $this->photos; + + if ($photos instanceof Collection) { + $photos = $photos->toArray(); + } + + if (! empty($photos)) { + $body = Arr::add(array: $body, key: 'photos', value: $photos); + } + + $videos = $this->videos; + + if ($videos instanceof Collection) { + $videos = $videos->toArray(); + } + + if (! empty($videos)) { + $body = Arr::add(array: $body, key: 'videos', value: $videos); + } + + $media = $this->media; + + if ($media instanceof Collection) { + $media = $media->toArray(); + } + + if (! empty($media)) { + $body = Arr::add(array: $body, key: 'media', value: $media); + } + + $options = $this->options; + + if ($options instanceof Collection) { + $options = $options->toArray(); + } + + if (! empty($options)) { + $body = Arr::add(array: $body, key: 'options', value: $options); + } + + return $body; + } + + public function createDtoFromResponse(Response $response): Post + { + return CreateAPostInAGivenStreamResponse::fromResponse($response); + } +} diff --git a/src/Responses/CreateAPostInAGivenStreamResponse.php b/src/Responses/CreateAPostInAGivenStreamResponse.php new file mode 100644 index 0000000..b402be1 --- /dev/null +++ b/src/Responses/CreateAPostInAGivenStreamResponse.php @@ -0,0 +1,37 @@ +json(); + + if (! $data) { + throw new Exception('No data found in response'); + } + + if (! $response->successful()) { + throw new Exception( + sprintf( + '%s: %s - %s', + 'Request was not successful: ', + Arr::get($data, 'error.code', 'Unknown Error Code'), + Arr::get($data, 'error.message', 'Unknown Error Message'), + ) + ); + } + + return Post::make($data); + } +} diff --git a/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php new file mode 100644 index 0000000..21cf50c --- /dev/null +++ b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php @@ -0,0 +1,278 @@ +send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!' + )); + + $post = $response->dto(); + + expect($post)->toBeInstanceOf(Post::class) + ->and($post->id)->toBeInt() + ->and($post->text)->toBeString() + ->and($post->streamId)->toBeInt() + ->and($post->userId)->toBeString() + ->and($post->uuid)->toBeString() + ->and($post->labels)->toBeInstanceOf(Collection::class) + ->and($post->files)->toBeInstanceOf(Collection::class) + ->and($post->photos)->toBeInstanceOf(Collection::class) + ->and($post->videos)->toBeInstanceOf(Collection::class) + ->and($post->media)->toBeInstanceOf(Collection::class) + ->and($post->options)->toBeInstanceOf(Collection::class) + ->and($post->mentions)->toBeInstanceOf(Collection::class); +})->group('streams'); + +test('can create a post in a given stream with all optional parameters', closure: function () { + $connector = new BeekeeperConnector; + + $fileData = [ + 'updated' => '2016-10-07T12:49:21', + 'name' => 'fair_play_rules.pdf', + 'created' => '2016-10-07T12:49:21', + 'url' => 'https://mytenant.beekeeper.io/file/665987/original/fair_play_rules.pdf', + 'userid' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'height' => 619, + 'width' => 700, + 'duration' => 315, + 'key' => 'f4fdaab0-d198-49b4-b1cc-dd85572d72f1', + 'media_type' => 'image/png', + 'usage_type' => 'attachment_image', + 'id' => 66598, + 'size' => 85, + ]; + + $response = $connector->send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!', + title: 'Hello guys!', + labels: ['food', 'poll', 'events'], + sticky: true, + locked: true, + reactionsDisabled: true, + scheduledAt: '2019-08-24T14:15:22', + files: [$fileData], + photos: [$fileData], + videos: [$fileData], + media: [$fileData], + options: [ + ['text' => 'This Friday'], + ['text' => 'Monday next week'], + ] + )); + + $post = $response->dto(); + + expect($post)->toBeInstanceOf(Post::class) + ->and($post->id)->toBeInt() + ->and($post->text)->toBe('Please indicate your preferred dates for next team event in the poll below. Thanks!') + ->and($post->title)->toBe('Hello guys!') + ->and($post->streamId)->toBe(6002) + ->and($post->labels)->toHaveCount(3) + ->and($post->labels->toArray())->toBe(['food', 'poll', 'events']) + ->and($post->sticky)->toBeTrue() + ->and($post->locked)->toBeTrue() + ->and($post->reactionsDisabled)->toBeTrue() + ->and($post->scheduledAt)->toBeInstanceOf(CarbonImmutable::class) + ->and($post->files)->toHaveCount(1) + ->and($post->files->first())->toBeInstanceOf(File::class) + ->and($post->photos)->toHaveCount(1) + ->and($post->photos->first())->toBeInstanceOf(File::class) + ->and($post->videos)->toHaveCount(1) + ->and($post->videos->first())->toBeInstanceOf(File::class) + ->and($post->media)->toHaveCount(1) + ->and($post->media->first())->toBeInstanceOf(File::class) + ->and($post->options)->toHaveCount(2) + ->and($post->options->first())->toBe(['text' => 'This Friday']) + ->and($post->options->last())->toBe(['text' => 'Monday next week']); +})->group('streams'); + +test('can create a post with collection parameters', closure: function () { + $connector = new BeekeeperConnector; + + $fileData = [ + 'updated' => '2016-10-07T12:49:21', + 'name' => 'test_file.pdf', + 'created' => '2016-10-07T12:49:21', + 'url' => 'https://mytenant.beekeeper.io/file/123456/original/test_file.pdf', + 'userid' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'height' => 100, + 'width' => 200, + 'duration' => 0, + 'key' => 'test-key-123', + 'media_type' => 'application/pdf', + 'usage_type' => 'attachment_file', + 'id' => 123456, + 'size' => 1024, + ]; + + $response = $connector->send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post with collections', + labels: collect(['test', 'collection']), + files: collect([$fileData]), + options: collect([ + ['text' => 'Option 1'], + ['text' => 'Option 2'], + ]) + )); + + $post = $response->dto(); + + expect($post)->toBeInstanceOf(Post::class) + ->and($post->text)->toBe('Test post with collections') + ->and($post->labels)->toHaveCount(2) + ->and($post->labels->toArray())->toBe(['test', 'collection']) + ->and($post->files)->toHaveCount(1) + ->and($post->files->first())->toBeInstanceOf(File::class) + ->and($post->options)->toHaveCount(2); +})->group('streams'); + +test('post response contains all expected fields', closure: function () { + $connector = new BeekeeperConnector; + + $response = $connector->send(new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post for field validation', + title: 'Test Title' + )); + + $post = $response->dto(); + + // Test core fields + expect($post->id)->toBeInt() + ->and($post->text)->toBeString() + ->and($post->title)->toBeString() + ->and($post->streamId)->toBeInt() + ->and($post->userId)->toBeString() + ->and($post->uuid)->toBeString() + ->and($post->type)->toBeString() + ->and($post->source)->toBeString(); + + // Test boolean fields + expect($post->sticky)->toBeBool() + ->and($post->locked)->toBeBool() + ->and($post->reactionsDisabled)->toBeBool() + ->and($post->moderated)->toBeBool() + ->and($post->edited)->toBeBool() + ->and($post->subscribedByUser)->toBeBool() + ->and($post->reportable)->toBeBool() + ->and($post->anonymous)->toBeBool() + ->and($post->unread)->toBeBool() + ->and($post->postedByUser)->toBeBool() + ->and($post->reportedByUser)->toBeBool() + ->and($post->likedByUser)->toBeBool(); + + // Test count fields + expect($post->likeCount)->toBeInt() + ->and($post->commentCount)->toBeInt() + ->and($post->reportCount)->toBeInt() + ->and($post->voteCount)->toBeInt() + ->and($post->digest)->toBeInt(); + + // Test collections + expect($post->labels)->toBeInstanceOf(Collection::class) + ->and($post->files)->toBeInstanceOf(Collection::class) + ->and($post->photos)->toBeInstanceOf(Collection::class) + ->and($post->videos)->toBeInstanceOf(Collection::class) + ->and($post->media)->toBeInstanceOf(Collection::class) + ->and($post->options)->toBeInstanceOf(Collection::class) + ->and($post->mentions)->toBeInstanceOf(Collection::class); + + // Test optional fields + expect($post->photo)->toBeStringOrNull() + ->and($post->languageConfidence)->toBeFloatOrNull() + ->and($post->metadata)->toBeStringOrNull() + ->and($post->profile)->toBeStringOrNull() + ->and($post->displayNameExtension)->toBeStringOrNull() + ->and($post->displayName)->toBeStringOrNull() + ->and($post->name)->toBeStringOrNull() + ->and($post->language)->toBeStringOrNull() + ->and($post->avatar)->toBeStringOrNull() + ->and($post->status)->toBeStringOrNull() + ->and($post->stateId)->toBeStringOrNull(); + + // Test timestamps + if ($post->created) { + expect($post->created)->toBeInstanceOf(CarbonImmutable::class); + } + if ($post->scheduledAt) { + expect($post->scheduledAt)->toBeInstanceOf(CarbonImmutable::class); + } +})->group('streams'); + +test('request body structure is correct', closure: function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post', + title: 'Test Title', + labels: ['test', 'labels'], + sticky: true, + locked: false, + reactionsDisabled: true, + scheduledAt: '2019-08-24T14:15:22', + files: [['test' => 'file']], + options: [['text' => 'Option 1']] + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultBody'); + $method->setAccessible(true); + $body = $method->invoke($request); + + expect($body)->toBeArray() + ->and($body)->toHaveKey('text') + ->and($body['text'])->toBe('Test post') + ->and($body)->toHaveKey('title') + ->and($body['title'])->toBe('Test Title') + ->and($body)->toHaveKey('labels') + ->and($body['labels'])->toBe(['test', 'labels']) + ->and($body)->toHaveKey('sticky') + ->and($body['sticky'])->toBeTrue() + ->and($body)->toHaveKey('reactions_disabled') + ->and($body['reactions_disabled'])->toBeTrue() + ->and($body)->toHaveKey('scheduled_at') + ->and($body['scheduled_at'])->toBe('2019-08-24T14:15:22') + ->and($body)->toHaveKey('files') + ->and($body['files'])->toBe([['test' => 'file']]) + ->and($body)->toHaveKey('options') + ->and($body['options'])->toBe([['text' => 'Option 1']]) + ->and($body)->not->toHaveKey('locked'); // Should not be included when false +})->group('streams'); + +test('request endpoint is correct', closure: function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post' + ); + + expect($request->resolveEndpoint())->toBe('/streams/6002/posts'); +})->group('streams'); + +test('request query parameters are correct', closure: function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post', + expand: ['user', 'stream'] + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultQuery'); + $method->setAccessible(true); + $query = $method->invoke($request); + + expect($query)->toBeArray() + ->and($query)->toHaveKey('expand') + ->and($query['expand'])->toBe('user,stream'); +})->group('streams'); diff --git a/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php b/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php new file mode 100644 index 0000000..fe86fa8 --- /dev/null +++ b/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php @@ -0,0 +1,390 @@ +resolveEndpoint())->toBe('/streams/6002/posts'); +})->group('unit'); + +test('can create request with all parameters', function () { + $fileData = [ + 'updated' => '2016-10-07T12:49:21', + 'name' => 'test_file.pdf', + 'created' => '2016-10-07T12:49:21', + 'url' => 'https://mytenant.beekeeper.io/file/123456/original/test_file.pdf', + 'userid' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'height' => 100, + 'width' => 200, + 'duration' => 0, + 'key' => 'test-key-123', + 'media_type' => 'application/pdf', + 'usage_type' => 'attachment_file', + 'id' => 123456, + 'size' => 1024, + ]; + + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post content', + title: 'Test Title', + labels: ['test', 'labels'], + sticky: true, + locked: true, + reactionsDisabled: true, + scheduledAt: '2019-08-24T14:15:22', + files: [$fileData], + photos: [$fileData], + videos: [$fileData], + media: [$fileData], + options: [ + ['text' => 'Option 1'], + ['text' => 'Option 2'], + ], + expand: ['user', 'stream'] + ); + + expect($request->resolveEndpoint())->toBe('/streams/6002/posts'); +})->group('unit'); + +test('request body contains correct data', function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post content', + title: 'Test Title', + labels: ['test', 'labels'], + sticky: true, + locked: false, + reactionsDisabled: true, + scheduledAt: '2019-08-24T14:15:22', + files: [['test' => 'file']], + options: [['text' => 'Option 1']] + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultBody'); + $method->setAccessible(true); + $body = $method->invoke($request); + + expect($body)->toBeArray() + ->and($body)->toHaveKey('text') + ->and($body['text'])->toBe('Test post content') + ->and($body)->toHaveKey('title') + ->and($body['title'])->toBe('Test Title') + ->and($body)->toHaveKey('labels') + ->and($body['labels'])->toBe(['test', 'labels']) + ->and($body)->toHaveKey('sticky') + ->and($body['sticky'])->toBeTrue() + ->and($body)->toHaveKey('reactions_disabled') + ->and($body['reactions_disabled'])->toBeTrue() + ->and($body)->toHaveKey('scheduled_at') + ->and($body['scheduled_at'])->toBe('2019-08-24T14:15:22') + ->and($body)->toHaveKey('files') + ->and($body['files'])->toBe([['test' => 'file']]) + ->and($body)->toHaveKey('options') + ->and($body['options'])->toBe([['text' => 'Option 1']]) + ->and($body)->not->toHaveKey('locked'); // Should not be included when false +})->group('unit'); + +test('request body handles collections correctly', function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post content', + labels: collect(['test', 'collection']), + files: collect([['test' => 'file']]), + options: collect([ + ['text' => 'Option 1'], + ['text' => 'Option 2'], + ]) + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultBody'); + $method->setAccessible(true); + $body = $method->invoke($request); + + expect($body)->toBeArray() + ->and($body['labels'])->toBe(['test', 'collection']) + ->and($body['files'])->toBe([['test' => 'file']]) + ->and($body['options'])->toBe([ + ['text' => 'Option 1'], + ['text' => 'Option 2'], + ]); +})->group('unit'); + +test('request query parameters are correct', function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post content', + expand: ['user', 'stream'] + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultQuery'); + $method->setAccessible(true); + $query = $method->invoke($request); + + expect($query)->toBeArray() + ->and($query)->toHaveKey('expand') + ->and($query['expand'])->toBe('user,stream'); +})->group('unit'); + +test('request query parameters handle collections', function () { + $request = new CreateAPostInAGivenStream( + streamId: '6002', + text: 'Test post content', + expand: collect(['user', 'stream', 'files']) + ); + + // Use reflection to access the protected method for testing + $reflection = new ReflectionClass($request); + $method = $reflection->getMethod('defaultQuery'); + $method->setAccessible(true); + $query = $method->invoke($request); + + expect($query)->toBeArray() + ->and($query)->toHaveKey('expand') + ->and($query['expand'])->toBe('user,stream,files'); +})->group('unit'); + +test('post data mapping works correctly', function () { + $postData = [ + 'id' => 2234, + 'text' => 'Please indicate your preferred dates for next team event in the poll below. Thanks!', + 'labels' => ['food', 'poll', 'events'], + 'sticky' => true, + 'like_count' => 42, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'uuid' => '731b28bc-7f10-4b68-a089-fc672abc9955', + 'title' => 'Hello guys!', + 'comment_count' => 2, + 'report_count' => 0, + 'source' => 'beekeeper', + 'vote_count' => 12, + 'moderated' => true, + 'photo' => 'https://d6698txzbomp3.cloudfront.net/72e3b7d4-c6a4-47e9-8f81-7b7d10bdd84a', + 'language_confidence' => 0.86, + 'type' => 'post', + 'metadata' => 'string', + 'profile' => 'peter_smith', + 'edited' => true, + 'display_name_extension' => 'General Manager', + 'subscribed_by_user' => true, + 'reportable' => true, + 'anonymous' => true, + 'display_name' => 'John Smith', + 'unread' => true, + 'locked' => true, + 'reactions_disabled' => true, + 'name' => 'Peter Smith', + 'language' => 'en', + 'language_information' => [ + 'language' => 'en', + 'confidence' => 0.99628, + 'reliable' => true, + ], + 'created' => '2016-10-07T12:48:27', + 'posted_by_user' => true, + 'avatar' => 'https://dz343oy86h947.cloudfront.net/business/neutral/normal/05.png', + 'reported_by_user' => true, + 'liked_by_user' => true, + 'mentions' => ['john_smith'], + 'mentions_details' => [ + 'smith_john' => 'Smith John', + ], + 'scheduled_at' => '2019-08-24T14:15:22', + 'status' => 'published', + 'files' => [ + [ + 'updated' => '2016-10-07T12:49:21', + 'name' => 'fair_play_rules.pdf', + 'created' => '2016-10-07T12:49:21', + 'url' => 'https://mytenant.beekeeper.io/file/665987/original/fair_play_rules.pdf', + 'userid' => '5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17', + 'height' => 619, + 'width' => 700, + 'duration' => 315, + 'key' => 'f4fdaab0-d198-49b4-b1cc-dd85572d72f1', + 'media_type' => 'image/png', + 'usage_type' => 'attachment_image', + 'id' => 66598, + 'size' => 85, + ], + ], + 'photos' => [], + 'videos' => [], + 'media' => [], + 'options' => [ + [ + 'text' => 'This Friday', + 'vote_count' => 12, + 'id' => 983, + ], + [ + 'text' => 'Monday next week', + 'vote_count' => 3, + 'id' => 984, + ], + ], + 'state_id' => '2017-06-19T08:49:53', + ]; + + $post = Post::make($postData); + + expect($post)->toBeInstanceOf(Post::class) + ->and($post->id)->toBe(2234) + ->and($post->text)->toBe('Please indicate your preferred dates for next team event in the poll below. Thanks!') + ->and($post->title)->toBe('Hello guys!') + ->and($post->labels)->toBeInstanceOf(Collection::class) + ->and($post->labels->toArray())->toBe(['food', 'poll', 'events']) + ->and($post->sticky)->toBeTrue() + ->and($post->likeCount)->toBe(42) + ->and($post->streamId)->toBe(6002) + ->and($post->userId)->toBe('5cb9v45d-8i78-4v65-b5fd-81cgfac3ef17') + ->and($post->uuid)->toBe('731b28bc-7f10-4b68-a089-fc672abc9955') + ->and($post->commentCount)->toBe(2) + ->and($post->reportCount)->toBe(0) + ->and($post->source)->toBe('beekeeper') + ->and($post->voteCount)->toBe(12) + ->and($post->moderated)->toBeTrue() + ->and($post->photo)->toBe('https://d6698txzbomp3.cloudfront.net/72e3b7d4-c6a4-47e9-8f81-7b7d10bdd84a') + ->and($post->languageConfidence)->toBe(0.86) + ->and($post->type)->toBe('post') + ->and($post->metadata)->toBe('string') + ->and($post->profile)->toBe('peter_smith') + ->and($post->edited)->toBeTrue() + ->and($post->displayNameExtension)->toBe('General Manager') + ->and($post->subscribedByUser)->toBeTrue() + ->and($post->reportable)->toBeTrue() + ->and($post->anonymous)->toBeTrue() + ->and($post->displayName)->toBe('John Smith') + ->and($post->unread)->toBeTrue() + ->and($post->locked)->toBeTrue() + ->and($post->reactionsDisabled)->toBeTrue() + ->and($post->name)->toBe('Peter Smith') + ->and($post->language)->toBe('en') + ->and($post->languageInformation)->toBeArray() + ->and($post->created)->toBeInstanceOf(\Carbon\CarbonImmutable::class) + ->and($post->postedByUser)->toBeTrue() + ->and($post->avatar)->toBe('https://dz343oy86h947.cloudfront.net/business/neutral/normal/05.png') + ->and($post->reportedByUser)->toBeTrue() + ->and($post->likedByUser)->toBeTrue() + ->and($post->mentions)->toBeInstanceOf(Collection::class) + ->and($post->mentions->toArray())->toBe(['john_smith']) + ->and($post->mentionsDetails)->toBeArray() + ->and($post->scheduledAt)->toBeInstanceOf(\Carbon\CarbonImmutable::class) + ->and($post->status)->toBe('published') + ->and($post->files)->toBeInstanceOf(Collection::class) + ->and($post->files)->toHaveCount(1) + ->and($post->files->first())->toBeInstanceOf(File::class) + ->and($post->photos)->toBeInstanceOf(Collection::class) + ->and($post->videos)->toBeInstanceOf(Collection::class) + ->and($post->media)->toBeInstanceOf(Collection::class) + ->and($post->options)->toBeInstanceOf(Collection::class) + ->and($post->options)->toHaveCount(2) + ->and($post->stateId)->toBe('2017-06-19T08:49:53'); +})->group('unit'); + +test('response class handles successful response', function () { + $postData = [ + 'id' => 2234, + 'text' => 'Test post content', + 'title' => 'Test Title', + 'labels' => ['test'], + 'sticky' => false, + 'like_count' => 0, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => 'test-user-id', + 'uuid' => 'test-uuid', + 'comment_count' => 0, + 'report_count' => 0, + 'source' => 'beekeeper', + 'vote_count' => 0, + 'moderated' => false, + 'photo' => null, + 'language_confidence' => null, + 'type' => 'post', + 'metadata' => null, + 'profile' => null, + 'edited' => false, + 'display_name_extension' => null, + 'subscribed_by_user' => false, + 'reportable' => false, + 'anonymous' => false, + 'display_name' => null, + 'unread' => false, + 'locked' => false, + 'reactions_disabled' => false, + 'name' => null, + 'language' => null, + 'language_information' => null, + 'created' => '2016-10-07T12:48:27', + 'posted_by_user' => false, + 'avatar' => null, + 'reported_by_user' => false, + 'liked_by_user' => false, + 'mentions' => [], + 'mentions_details' => null, + 'scheduled_at' => null, + 'status' => null, + 'files' => [], + 'photos' => [], + 'videos' => [], + 'media' => [], + 'options' => [], + 'state_id' => null, + ]; + + // Mock a successful response + $mockResponse = Mockery::mock(Response::class); + $mockResponse->shouldReceive('json')->andReturn($postData); + $mockResponse->shouldReceive('successful')->andReturn(true); + + $post = CreateAPostInAGivenStreamResponse::fromResponse($mockResponse); + + expect($post)->toBeInstanceOf(Post::class) + ->and($post->id)->toBe(2234) + ->and($post->text)->toBe('Test post content') + ->and($post->title)->toBe('Test Title'); +})->group('unit'); + +test('response class handles error response', function () { + $errorData = [ + 'error' => [ + 'code' => 'VALIDATION_ERROR', + 'message' => 'Invalid request data', + ], + ]; + + // Mock an error response + $mockResponse = Mockery::mock(Response::class); + $mockResponse->shouldReceive('json')->andReturn($errorData); + $mockResponse->shouldReceive('successful')->andReturn(false); + + expect(fn () => CreateAPostInAGivenStreamResponse::fromResponse($mockResponse)) + ->toThrow(Exception::class, 'Request was not successful: : VALIDATION_ERROR - Invalid request data'); +})->group('unit'); + +test('response class handles empty response', function () { + // Mock an empty response + $mockResponse = Mockery::mock(Response::class); + $mockResponse->shouldReceive('json')->andReturn(null); + + expect(fn () => CreateAPostInAGivenStreamResponse::fromResponse($mockResponse)) + ->toThrow(Exception::class, 'No data found in response'); +})->group('unit'); From 497a122b7b7535f302acb5b775b3a4c771aab52e Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 12:24:46 +0200 Subject: [PATCH 2/4] wip --- src/Data/Configs/General.php | 2 +- .../CreateAChildToAnArtifactRequestTest.php | 27 +- .../CreateAPostInAGivenStreamRequestTest.php | 246 ++++++++++++++++-- ...etStatusOfAuthenticatedUserRequestTest.php | 37 ++- .../Requests/ListArtifactsRequestTest.php | 22 +- .../Requests/UploadAFileRequestTest.php | 25 +- tests/TestCase.php | 2 + 7 files changed, 336 insertions(+), 25 deletions(-) diff --git a/src/Data/Configs/General.php b/src/Data/Configs/General.php index 39eed5a..dfc836c 100755 --- a/src/Data/Configs/General.php +++ b/src/Data/Configs/General.php @@ -27,7 +27,7 @@ public static function make(array $data): self public function __construct( public int $id, - public string $companyAccount, + public ?string $companyAccount, public string $name, public string $language, public CarbonImmutable $created, diff --git a/tests/Feature/Requests/CreateAChildToAnArtifactRequestTest.php b/tests/Feature/Requests/CreateAChildToAnArtifactRequestTest.php index cc0d184..e1d1f14 100755 --- a/tests/Feature/Requests/CreateAChildToAnArtifactRequestTest.php +++ b/tests/Feature/Requests/CreateAChildToAnArtifactRequestTest.php @@ -6,10 +6,35 @@ use CodebarAg\LaravelBeekeeper\Enums\Artifacts\Type; use CodebarAg\LaravelBeekeeper\Requests\CreateAChildToAnArtifact; use Illuminate\Support\Collection; +use Saloon\Http\Faking\MockResponse; +use Saloon\Laravel\Facades\Saloon; test('can create a child to an artifact', closure: function () { - $connector = new BeekeeperConnector; + Saloon::fake([ + CreateAChildToAnArtifact::class => MockResponse::make([ + 'id' => '1b574087-e428-4640-a6c1-37a62fbf357f', + 'tenantId' => 'tenant-123', + 'name' => 'test-file.png', + 'type' => 'file', + 'parentId' => '9a6d0642-fb9d-4f0f-9720-de00edbf7b0b', + 'metadata' => [ + 'mimeType' => 'image/png', + 'url' => 'https://codebar.us.beekeeper.io/api/2/files/key/dc8da887-fec5-4022-914e-fa34091f3485', + 'userId' => 'd84bed15-2a56-41b4-8e6a-7ee731e2ce34', + 'key' => 'dc8da887-fec5-4022-914e-fa34091f3485', + 'id' => 22189080, + 'size' => 235978, + ], + 'createdAt' => '2023-01-01T00:00:00Z', + 'updatedAt' => '2023-01-01T00:00:00Z', + 'breadcrumbs' => [], + 'children' => [], + 'acl' => [], + 'filterData' => [], + ], 200), + ]); + $connector = new BeekeeperConnector; $response = $connector->send(new CreateAChildToAnArtifact( artifactId: '1b574087-e428-4640-a6c1-37a62fbf357f', name: Str::random(23).'.png', diff --git a/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php index 21cf50c..764ac67 100644 --- a/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php +++ b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php @@ -6,10 +6,63 @@ use CodebarAg\LaravelBeekeeper\Data\Streams\Post; use CodebarAg\LaravelBeekeeper\Requests\CreateAPostInAGivenStream; use Illuminate\Support\Collection; +use Saloon\Http\Faking\MockResponse; +use Saloon\Laravel\Facades\Saloon; test('can create a post in a given stream with minimal data', closure: function () { - $connector = new BeekeeperConnector; + Saloon::fake([ + CreateAPostInAGivenStream::class => MockResponse::make([ + 'id' => 12345, + 'text' => 'Please indicate your preferred dates for next team event in the poll below. Thanks!', + 'title' => null, + 'labels' => [], + 'sticky' => false, + 'like_count' => 0, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => 'user-123', + 'uuid' => 'post-uuid-123', + 'comment_count' => 0, + 'report_count' => 0, + 'source' => 'web', + 'vote_count' => 0, + 'moderated' => false, + 'photo' => null, + 'language_confidence' => null, + 'type' => 'post', + 'metadata' => null, + 'profile' => null, + 'edited' => false, + 'display_name_extension' => null, + 'subscribed_by_user' => false, + 'reportable' => true, + 'anonymous' => false, + 'display_name' => null, + 'unread' => false, + 'locked' => false, + 'reactions_disabled' => false, + 'name' => null, + 'language' => null, + 'language_information' => null, + 'created' => '2023-01-01T00:00:00Z', + 'posted_by_user' => true, + 'avatar' => null, + 'reported_by_user' => false, + 'liked_by_user' => false, + 'mentions' => [], + 'mentions_details' => null, + 'scheduled_at' => null, + 'status' => null, + 'files' => [], + 'photos' => [], + 'videos' => [], + 'media' => [], + 'options' => [], + 'state_id' => null, + ], 200), + ]); + $connector = new BeekeeperConnector; $response = $connector->send(new CreateAPostInAGivenStream( streamId: '6002', text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!' @@ -33,8 +86,6 @@ })->group('streams'); test('can create a post in a given stream with all optional parameters', closure: function () { - $connector = new BeekeeperConnector; - $fileData = [ 'updated' => '2016-10-07T12:49:21', 'name' => 'fair_play_rules.pdf', @@ -51,6 +102,62 @@ 'size' => 85, ]; + Saloon::fake([ + CreateAPostInAGivenStream::class => MockResponse::make([ + 'id' => 12345, + 'text' => 'Please indicate your preferred dates for next team event in the poll below. Thanks!', + 'title' => 'Hello guys!', + 'labels' => ['food', 'poll', 'events'], + 'sticky' => true, + 'like_count' => 0, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => 'user-123', + 'uuid' => 'post-uuid-123', + 'comment_count' => 0, + 'report_count' => 0, + 'source' => 'web', + 'vote_count' => 0, + 'moderated' => false, + 'photo' => null, + 'language_confidence' => null, + 'type' => 'post', + 'metadata' => null, + 'profile' => null, + 'edited' => false, + 'display_name_extension' => null, + 'subscribed_by_user' => false, + 'reportable' => true, + 'anonymous' => false, + 'display_name' => null, + 'unread' => false, + 'locked' => true, + 'reactions_disabled' => true, + 'name' => null, + 'language' => null, + 'language_information' => null, + 'created' => '2023-01-01T00:00:00Z', + 'posted_by_user' => true, + 'avatar' => null, + 'reported_by_user' => false, + 'liked_by_user' => false, + 'mentions' => [], + 'mentions_details' => null, + 'scheduled_at' => '2019-08-24T14:15:22Z', + 'status' => null, + 'files' => [$fileData], + 'photos' => [$fileData], + 'videos' => [$fileData], + 'media' => [$fileData], + 'options' => [ + ['text' => 'This Friday'], + ['text' => 'Monday next week'], + ], + 'state_id' => null, + ], 200), + ]); + + $connector = new BeekeeperConnector; $response = $connector->send(new CreateAPostInAGivenStream( streamId: '6002', text: 'Please indicate your preferred dates for next team event in the poll below. Thanks!', @@ -97,8 +204,6 @@ })->group('streams'); test('can create a post with collection parameters', closure: function () { - $connector = new BeekeeperConnector; - $fileData = [ 'updated' => '2016-10-07T12:49:21', 'name' => 'test_file.pdf', @@ -115,6 +220,62 @@ 'size' => 1024, ]; + Saloon::fake([ + CreateAPostInAGivenStream::class => MockResponse::make([ + 'id' => 12345, + 'text' => 'Test post with collections', + 'title' => null, + 'labels' => ['test', 'collection'], + 'sticky' => false, + 'like_count' => 0, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => 'user-123', + 'uuid' => 'post-uuid-123', + 'comment_count' => 0, + 'report_count' => 0, + 'source' => 'web', + 'vote_count' => 0, + 'moderated' => false, + 'photo' => null, + 'language_confidence' => null, + 'type' => 'post', + 'metadata' => null, + 'profile' => null, + 'edited' => false, + 'display_name_extension' => null, + 'subscribed_by_user' => false, + 'reportable' => true, + 'anonymous' => false, + 'display_name' => null, + 'unread' => false, + 'locked' => false, + 'reactions_disabled' => false, + 'name' => null, + 'language' => null, + 'language_information' => null, + 'created' => '2023-01-01T00:00:00Z', + 'posted_by_user' => true, + 'avatar' => null, + 'reported_by_user' => false, + 'liked_by_user' => false, + 'mentions' => [], + 'mentions_details' => null, + 'scheduled_at' => null, + 'status' => null, + 'files' => [$fileData], + 'photos' => [], + 'videos' => [], + 'media' => [], + 'options' => [ + ['text' => 'Option 1'], + ['text' => 'Option 2'], + ], + 'state_id' => null, + ], 200), + ]); + + $connector = new BeekeeperConnector; $response = $connector->send(new CreateAPostInAGivenStream( streamId: '6002', text: 'Test post with collections', @@ -138,8 +299,59 @@ })->group('streams'); test('post response contains all expected fields', closure: function () { - $connector = new BeekeeperConnector; + Saloon::fake([ + CreateAPostInAGivenStream::class => MockResponse::make([ + 'id' => 12345, + 'text' => 'Test post for field validation', + 'title' => 'Test Title', + 'labels' => [], + 'sticky' => false, + 'like_count' => 5, + 'streamid' => 6002, + 'digest' => 1, + 'user_id' => 'user-123', + 'uuid' => 'post-uuid-123', + 'comment_count' => 3, + 'report_count' => 0, + 'source' => 'web', + 'vote_count' => 2, + 'moderated' => false, + 'photo' => 'photo-url', + 'language_confidence' => 0.95, + 'type' => 'post', + 'metadata' => '{"key": "value"}', + 'profile' => 'profile-123', + 'edited' => false, + 'display_name_extension' => 'Jr.', + 'subscribed_by_user' => true, + 'reportable' => true, + 'anonymous' => false, + 'display_name' => 'John Doe', + 'unread' => false, + 'locked' => false, + 'reactions_disabled' => false, + 'name' => 'John', + 'language' => 'en', + 'language_information' => ['confidence' => 0.95], + 'created' => '2023-01-01T00:00:00Z', + 'posted_by_user' => true, + 'avatar' => 'https://example.com/avatar.jpg', + 'reported_by_user' => false, + 'liked_by_user' => true, + 'mentions' => [], + 'mentions_details' => null, + 'scheduled_at' => null, + 'status' => 'published', + 'files' => [], + 'photos' => [], + 'videos' => [], + 'media' => [], + 'options' => [], + 'state_id' => 'state-123', + ], 200), + ]); + $connector = new BeekeeperConnector; $response = $connector->send(new CreateAPostInAGivenStream( streamId: '6002', text: 'Test post for field validation', @@ -189,17 +401,17 @@ ->and($post->mentions)->toBeInstanceOf(Collection::class); // Test optional fields - expect($post->photo)->toBeStringOrNull() - ->and($post->languageConfidence)->toBeFloatOrNull() - ->and($post->metadata)->toBeStringOrNull() - ->and($post->profile)->toBeStringOrNull() - ->and($post->displayNameExtension)->toBeStringOrNull() - ->and($post->displayName)->toBeStringOrNull() - ->and($post->name)->toBeStringOrNull() - ->and($post->language)->toBeStringOrNull() - ->and($post->avatar)->toBeStringOrNull() - ->and($post->status)->toBeStringOrNull() - ->and($post->stateId)->toBeStringOrNull(); + expect($post->photo)->toBeString() + ->and($post->languageConfidence)->toBeFloat() + ->and($post->metadata)->toBeString() + ->and($post->profile)->toBeString() + ->and($post->displayNameExtension)->toBeString() + ->and($post->displayName)->toBeString() + ->and($post->name)->toBeString() + ->and($post->language)->toBeString() + ->and($post->avatar)->toBeString() + ->and($post->status)->toBeString() + ->and($post->stateId)->toBeString(); // Test timestamps if ($post->created) { diff --git a/tests/Feature/Requests/GetStatusOfAuthenticatedUserRequestTest.php b/tests/Feature/Requests/GetStatusOfAuthenticatedUserRequestTest.php index e5ed9da..4208706 100755 --- a/tests/Feature/Requests/GetStatusOfAuthenticatedUserRequestTest.php +++ b/tests/Feature/Requests/GetStatusOfAuthenticatedUserRequestTest.php @@ -6,12 +6,44 @@ use CodebarAg\LaravelBeekeeper\Data\Configs\General; use CodebarAg\LaravelBeekeeper\Requests\GetStatusOfAuthenticatedUserRequest; use Illuminate\Support\Collection; +use Saloon\Http\Faking\MockResponse; +use Saloon\Laravel\Facades\Saloon; test('can get status of authenticated user', function () { - $connector = new BeekeeperConnector; + Saloon::fake([ + GetStatusOfAuthenticatedUserRequest::class => MockResponse::make([ + 'max_file_size' => 10485760, + 'max_files_on_post' => 10, + 'max_photo_size' => 5242880, + 'max_media_on_post' => 5, + 'max_video_size' => 104857600, + 'max_video_size_for_admins' => 209715200, + 'max_voice_recording_length' => 300, + 'max_users_in_group_chat' => 50, + 'reactions' => ['like', 'love', 'laugh', 'wow', 'sad', 'angry'], + 'feature_flags' => ['feature1', 'feature2'], + 'integrations' => ['integration1', 'integration2'], + 'styling' => ['theme1', 'theme2'], + 'tracking' => ['tracking1', 'tracking2'], + 'general' => [ + 'id' => 1, + 'company_account' => 'test-company', + 'name' => 'Test Company', + 'language' => 'en', + 'created' => '2023-01-01T00:00:00Z', + 'url' => 'https://test.beekeeper.io', + 'tagline' => 'Test Tagline', + 'fqdn' => 'test.beekeeper.io', + 'support_email' => 'support@test.com', + 'is_data_security_contact_set' => true, + 'timezone' => 'UTC', + 'subdomain' => 'test', + ], + ], 200), + ]); + $connector = new BeekeeperConnector; $response = $connector->send(new GetStatusOfAuthenticatedUserRequest); - $userStatus = $response->dto(); expect($userStatus)->toBeInstanceOf(AuthenticatedUserStatus::class) @@ -23,7 +55,6 @@ ->and($userStatus->maxVideoSizeForAdmins)->toBeInt() ->and($userStatus->maxVoiceRecordingLength)->toBeInt() ->and($userStatus->maxUsersInGroupChat)->toBeInt() - ->and($userStatus->reactions)->toBeInstanceOf(Collection::class) ->and($userStatus->featureFlags)->toBeInstanceOf(Collection::class) ->and($userStatus->integrations)->toBeInstanceOf(Collection::class) ->and($userStatus->styling)->toBeInstanceOf(Collection::class) diff --git a/tests/Feature/Requests/ListArtifactsRequestTest.php b/tests/Feature/Requests/ListArtifactsRequestTest.php index ac13f9f..bea0b19 100755 --- a/tests/Feature/Requests/ListArtifactsRequestTest.php +++ b/tests/Feature/Requests/ListArtifactsRequestTest.php @@ -7,10 +7,30 @@ use CodebarAg\LaravelBeekeeper\Enums\Artifacts\Type; use CodebarAg\LaravelBeekeeper\Requests\ListArtifacts; use Illuminate\Support\Collection; +use Saloon\Http\Faking\MockResponse; +use Saloon\Laravel\Facades\Saloon; test('can list artifacts', function () { - $connector = new BeekeeperConnector; + Saloon::fake([ + ListArtifacts::class => MockResponse::make([ + [ + 'id' => 'artifact-123', + 'tenantId' => 'tenant-123', + 'name' => 'Test Folder', + 'type' => 'folder', + 'parentId' => null, + 'metadata' => [], + 'createdAt' => '2023-01-01T00:00:00Z', + 'updatedAt' => '2023-01-01T00:00:00Z', + 'breadcrumbs' => [], + 'children' => [], + 'acl' => [], + 'filterData' => [], + ], + ], 200), + ]); + $connector = new BeekeeperConnector; $response = $connector->send(new ListArtifacts( type: Type::FOLDER, sort: Sort::NAME_ASC, diff --git a/tests/Feature/Requests/UploadAFileRequestTest.php b/tests/Feature/Requests/UploadAFileRequestTest.php index c22e6ea..c6d550f 100755 --- a/tests/Feature/Requests/UploadAFileRequestTest.php +++ b/tests/Feature/Requests/UploadAFileRequestTest.php @@ -7,13 +7,34 @@ use CodebarAg\LaravelBeekeeper\Enums\Files\UsageType; use CodebarAg\LaravelBeekeeper\Requests\UploadAFileRequest; use Illuminate\Support\Collection; +use Saloon\Http\Faking\MockResponse; +use Saloon\Laravel\Facades\Saloon; test('can upload a file', function () { - $connector = new BeekeeperConnector; - $fileContent = file_get_contents(__DIR__.'/../../Fixtures/files/test-1.pdf'); $fileName = 'test-1.pdf'; + Saloon::fake([ + UploadAFileRequest::class => MockResponse::make([ + 'name' => $fileName, + 'status' => 'ready', + 'created' => '2023-01-01T00:00:00Z', + 'updated' => '2023-01-01T00:00:00Z', + 'url' => 'https://example.com/file.pdf', + 'userid' => 'user-123', + 'width' => null, + 'height' => null, + 'key' => 'file-key-123', + 'duration' => null, + 'media_type' => 'application/pdf', + 'usage_type' => 'attachment_file', + 'id' => 12345, + 'size' => strlen($fileContent), + 'versions' => [], + ], 200), + ]); + + $connector = new BeekeeperConnector; $response = $connector->send(new UploadAFileRequest( fileContent: $fileContent, fileName: $fileName, diff --git a/tests/TestCase.php b/tests/TestCase.php index 0530cc2..19b8368 100755 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,6 +5,7 @@ use CodebarAg\LaravelBeekeeper\LaravelBeekeeperServiceProvider; use Illuminate\Database\Eloquent\Factories\Factory; use Orchestra\Testbench\TestCase as Orchestra; +use Saloon\Laravel\SaloonServiceProvider; class TestCase extends Orchestra { @@ -21,6 +22,7 @@ protected function getPackageProviders($app): array { return [ LaravelBeekeeperServiceProvider::class, + SaloonServiceProvider::class, ]; } From f8a7af47ce4d10c5ac4904b7a85e0ad879a469ee Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Tue, 28 Oct 2025 00:12:07 +0100 Subject: [PATCH 3/4] Updated Beekeper Requests --- src/Requests/CreateAPostInAGivenStream.php | 22 ---------------------- src/Requests/DeleteAnArtifact.php | 20 ++++++++++++++++++++ src/Requests/UploadAFileRequest.php | 11 +++++++---- 3 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 src/Requests/DeleteAnArtifact.php diff --git a/src/Requests/CreateAPostInAGivenStream.php b/src/Requests/CreateAPostInAGivenStream.php index 793a3a8..9bcdc8f 100644 --- a/src/Requests/CreateAPostInAGivenStream.php +++ b/src/Requests/CreateAPostInAGivenStream.php @@ -28,8 +28,6 @@ public function __construct( protected readonly bool $reactionsDisabled = false, protected readonly ?string $scheduledAt = null, protected null|array|Collection $files = null, - protected null|array|Collection $photos = null, - protected null|array|Collection $videos = null, protected null|array|Collection $media = null, protected null|array|Collection $options = null, protected readonly array|Collection $expand = [], @@ -99,26 +97,6 @@ public function defaultBody(): array $body = Arr::add(array: $body, key: 'files', value: $files); } - $photos = $this->photos; - - if ($photos instanceof Collection) { - $photos = $photos->toArray(); - } - - if (! empty($photos)) { - $body = Arr::add(array: $body, key: 'photos', value: $photos); - } - - $videos = $this->videos; - - if ($videos instanceof Collection) { - $videos = $videos->toArray(); - } - - if (! empty($videos)) { - $body = Arr::add(array: $body, key: 'videos', value: $videos); - } - $media = $this->media; if ($media instanceof Collection) { diff --git a/src/Requests/DeleteAnArtifact.php b/src/Requests/DeleteAnArtifact.php new file mode 100644 index 0000000..1041a26 --- /dev/null +++ b/src/Requests/DeleteAnArtifact.php @@ -0,0 +1,20 @@ +artifactId; + } +} diff --git a/src/Requests/UploadAFileRequest.php b/src/Requests/UploadAFileRequest.php index b353059..1d5c7fd 100644 --- a/src/Requests/UploadAFileRequest.php +++ b/src/Requests/UploadAFileRequest.php @@ -18,21 +18,24 @@ class UploadAFileRequest extends Request implements HasBody protected Method $method = Method::POST; public function __construct( - protected readonly ?string $fileContent, - protected readonly ?string $fileName, + protected readonly string $fileContent, + protected readonly string $fileName, + protected readonly string $usageType, ) {} public function resolveEndpoint(): string { - return '/files'; + return '/files/'.$this->usageType; } protected function defaultBody(): array { - return [ + $defaultBody = [ new MultipartValue(name: 'file', value: $this->fileContent, filename: $this->fileName), ]; + + return $defaultBody; } public function createDtoFromResponse(Response $response): File From 003c1d513fc36380390a5d5bf97cc77924d292c1 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Tue, 28 Oct 2025 00:17:06 +0100 Subject: [PATCH 4/4] Updated Tests + readme.md --- README.md | 16 ++++++++++++++-- .../CreateAPostInAGivenStreamRequestTest.php | 8 -------- .../Requests/DeleteAnArtifactRequestTest.php | 19 +++++++++++++++++++ .../Requests/UploadAFileRequestTest.php | 1 + .../CreateAPostInAGivenStreamRequestTest.php | 4 ---- 5 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 tests/Feature/Requests/DeleteAnArtifactRequestTest.php diff --git a/README.md b/README.md index 736618b..b814aa7 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ files, streams, and posts. * [List Artifacts](#list-artifacts) * [Upload A File](#upload-a-file) * [Create A Child To An Artifact](#create-a-child-to-an-artifact) + * [Delete An Artifact](#delete-an-artifact) * [Create A Post In A Given Stream](#create-a-post-in-a-given-stream) * [DTO Showcase](#dto-showcase) * [Available Enums](#available-enums) @@ -141,6 +142,7 @@ $fileName = 'foobar.pdf'; $response = $connector->send(new UploadAFileRequest( fileContent: $fileContent, fileName: $fileName, + usageType: 'attachment_file', )); ``` @@ -168,6 +170,18 @@ $response = $connector->send(new CreateAChildToAnArtifact( )); ``` +### Delete An Artifact + +```php +use CodebarAg\LaravelBeekeeper\Requests\DeleteAnArtifact; + +$response = $connector->send(new DeleteAnArtifact( + artifactId: '12345678-abcd-efgh-9012-de00edbf7b0b' +)); + +// Returns a 204 No Content response on success +``` + ### Create A Post In A Given Stream ```php @@ -206,8 +220,6 @@ $response = $connector->send(new CreateAPostInAGivenStream( reactionsDisabled: true, scheduledAt: '2019-08-24T14:15:22', files: [$fileData], - photos: [$fileData], - videos: [$fileData], media: [$fileData], options: [ ['text' => 'This Friday'], diff --git a/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php index 764ac67..1c2bd34 100644 --- a/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php +++ b/tests/Feature/Requests/CreateAPostInAGivenStreamRequestTest.php @@ -78,8 +78,6 @@ ->and($post->uuid)->toBeString() ->and($post->labels)->toBeInstanceOf(Collection::class) ->and($post->files)->toBeInstanceOf(Collection::class) - ->and($post->photos)->toBeInstanceOf(Collection::class) - ->and($post->videos)->toBeInstanceOf(Collection::class) ->and($post->media)->toBeInstanceOf(Collection::class) ->and($post->options)->toBeInstanceOf(Collection::class) ->and($post->mentions)->toBeInstanceOf(Collection::class); @@ -168,8 +166,6 @@ reactionsDisabled: true, scheduledAt: '2019-08-24T14:15:22', files: [$fileData], - photos: [$fileData], - videos: [$fileData], media: [$fileData], options: [ ['text' => 'This Friday'], @@ -192,10 +188,6 @@ ->and($post->scheduledAt)->toBeInstanceOf(CarbonImmutable::class) ->and($post->files)->toHaveCount(1) ->and($post->files->first())->toBeInstanceOf(File::class) - ->and($post->photos)->toHaveCount(1) - ->and($post->photos->first())->toBeInstanceOf(File::class) - ->and($post->videos)->toHaveCount(1) - ->and($post->videos->first())->toBeInstanceOf(File::class) ->and($post->media)->toHaveCount(1) ->and($post->media->first())->toBeInstanceOf(File::class) ->and($post->options)->toHaveCount(2) diff --git a/tests/Feature/Requests/DeleteAnArtifactRequestTest.php b/tests/Feature/Requests/DeleteAnArtifactRequestTest.php new file mode 100644 index 0000000..a60789a --- /dev/null +++ b/tests/Feature/Requests/DeleteAnArtifactRequestTest.php @@ -0,0 +1,19 @@ + MockResponse::make([], 204), + ]); + + $connector = new BeekeeperConnector; + $response = $connector->send(new DeleteAnArtifact( + artifactId: '12345678-abcd-efgh-9012-de00edbf7b0b' + )); + + expect($response->status())->toBe(204); +})->group('artifacts'); diff --git a/tests/Feature/Requests/UploadAFileRequestTest.php b/tests/Feature/Requests/UploadAFileRequestTest.php index c6d550f..1671a0a 100755 --- a/tests/Feature/Requests/UploadAFileRequestTest.php +++ b/tests/Feature/Requests/UploadAFileRequestTest.php @@ -38,6 +38,7 @@ $response = $connector->send(new UploadAFileRequest( fileContent: $fileContent, fileName: $fileName, + usageType: 'attachment_file', )); $uploadAFile = $response->dto(); diff --git a/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php b/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php index fe86fa8..5f28b32 100644 --- a/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php +++ b/tests/Unit/Requests/CreateAPostInAGivenStreamRequestTest.php @@ -43,8 +43,6 @@ reactionsDisabled: true, scheduledAt: '2019-08-24T14:15:22', files: [$fileData], - photos: [$fileData], - videos: [$fileData], media: [$fileData], options: [ ['text' => 'Option 1'], @@ -291,8 +289,6 @@ ->and($post->files)->toBeInstanceOf(Collection::class) ->and($post->files)->toHaveCount(1) ->and($post->files->first())->toBeInstanceOf(File::class) - ->and($post->photos)->toBeInstanceOf(Collection::class) - ->and($post->videos)->toBeInstanceOf(Collection::class) ->and($post->media)->toBeInstanceOf(Collection::class) ->and($post->options)->toBeInstanceOf(Collection::class) ->and($post->options)->toHaveCount(2)