feat(posthog): keep social_accounts_count and posts_count fresh on account group
Onboarding/lifecycle workflows in PostHog (and downstream tools like SendKit) need to segment users by how many social accounts they've connected and how many posts they've created. The existing SyncUser job only re-emitted these counts on signup and billing changes, so the values went stale the moment a user did anything meaningful. This wires up two new paths that refresh the account group automatically: - SocialAccountObserver (#[ObservedBy] on the model) fires SyncAccountUsage on created/deleted, covering all 14 OAuth callback paths in one hook. - SyncUsageOnPostCreated / SyncUsageOnPostDeleted listeners (auto-discovered) fire SyncAccountUsage on the corresponding events dispatched by CreatePost and DeletePost. SyncAccountUsage is the new dedicated job for group properties only (groupIdentify account + workspace). SyncUser was slimmed to just identify the user and delegate the group sync, removing the duplicated property mapping between the two jobs. All entry points (observer + both listeners) short-circuit when PostHog is disabled, so self-hosted instances without PostHog configured see zero queued jobs and zero overhead. posts_count cache is invalidated before each sync so the job reads fresh counts from the database instead of stale cached values.
This commit is contained in:
parent
626da2c4dd
commit
4debc97cb0
11 changed files with 502 additions and 78 deletions
78
app/Jobs/PostHog/SyncAccountUsage.php
Normal file
78
app/Jobs/PostHog/SyncAccountUsage.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs\PostHog;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\PostHogService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SyncAccountUsage implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
public int $timeout = 30;
|
||||
|
||||
public function __construct(public string $accountId, public ?string $workspaceId = null)
|
||||
{
|
||||
$this->onQueue('posthog');
|
||||
}
|
||||
|
||||
public function handle(PostHogService $postHog): void
|
||||
{
|
||||
if (! PostHogService::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Cache::forget("account:{$this->accountId}:posts_count");
|
||||
|
||||
$account = Account::with('plan')->find($this->accountId);
|
||||
|
||||
if (! $account) {
|
||||
return;
|
||||
}
|
||||
|
||||
$usage = $account->usage();
|
||||
|
||||
$postHog->groupIdentify('account', (string) $account->id, [
|
||||
'name' => $account->name,
|
||||
'plan' => $account->plan?->name,
|
||||
'plan_slug' => $account->plan?->slug->value,
|
||||
'has_active_subscription' => $account->hasActiveSubscription(),
|
||||
'is_on_trial' => $account->isOnTrial(),
|
||||
'workspaces_count' => $usage['workspaceCount'],
|
||||
'members_count' => $usage['memberCount'],
|
||||
'social_accounts_count' => $usage['socialAccountCount'],
|
||||
'posts_count' => $usage['postCount'],
|
||||
'pending_invites_count' => $usage['pendingInviteCount'],
|
||||
'credits_used' => $usage['creditsUsed'],
|
||||
'created_at' => $account->created_at?->toIso8601String(),
|
||||
]);
|
||||
|
||||
if (! $this->workspaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workspace = Workspace::withCount('socialAccounts')->find($this->workspaceId);
|
||||
|
||||
if (! $workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$postHog->groupIdentify('workspace', (string) $workspace->id, [
|
||||
'name' => $workspace->name,
|
||||
'account_id' => (string) $workspace->account_id,
|
||||
'social_accounts_count' => (int) $workspace->social_accounts_count,
|
||||
'created_at' => $workspace->created_at?->toIso8601String(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,10 +31,7 @@ public function handle(PostHogService $postHog): void
|
|||
return;
|
||||
}
|
||||
|
||||
$user = User::with([
|
||||
'account.plan',
|
||||
'currentWorkspace' => fn ($query) => $query->withCount('socialAccounts'),
|
||||
])->find($this->userId);
|
||||
$user = User::find($this->userId);
|
||||
|
||||
if (! $user) {
|
||||
return;
|
||||
|
|
@ -46,32 +43,11 @@ public function handle(PostHogService $postHog): void
|
|||
'$set_once' => ['signed_up_at' => $user->created_at?->toIso8601String()],
|
||||
]);
|
||||
|
||||
if ($account = $user->account) {
|
||||
$usage = $account->usage();
|
||||
|
||||
$postHog->groupIdentify('account', (string) $account->id, [
|
||||
'name' => $account->name,
|
||||
'plan' => $account->plan?->name,
|
||||
'plan_slug' => $account->plan?->slug->value,
|
||||
'has_active_subscription' => $account->hasActiveSubscription(),
|
||||
'is_on_trial' => $account->isOnTrial(),
|
||||
'workspaces_count' => $usage['workspaceCount'],
|
||||
'members_count' => $usage['memberCount'],
|
||||
'social_accounts_count' => $usage['socialAccountCount'],
|
||||
'posts_count' => $usage['postCount'],
|
||||
'pending_invites_count' => $usage['pendingInviteCount'],
|
||||
'credits_used' => $usage['creditsUsed'],
|
||||
'created_at' => $account->created_at?->toIso8601String(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($workspace = $user->currentWorkspace) {
|
||||
$postHog->groupIdentify('workspace', (string) $workspace->id, [
|
||||
'name' => $workspace->name,
|
||||
'account_id' => (string) $workspace->account_id,
|
||||
'social_accounts_count' => (int) $workspace->social_accounts_count,
|
||||
'created_at' => $workspace->created_at?->toIso8601String(),
|
||||
]);
|
||||
if ($user->account_id) {
|
||||
SyncAccountUsage::dispatch(
|
||||
(string) $user->account_id,
|
||||
$user->current_workspace_id ? (string) $user->current_workspace_id : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
app/Listeners/PostHog/SyncUsageOnPostCreated.php
Normal file
23
app/Listeners/PostHog/SyncUsageOnPostCreated.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners\PostHog;
|
||||
|
||||
use App\Events\PostCreated;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Services\PostHogService;
|
||||
|
||||
class SyncUsageOnPostCreated
|
||||
{
|
||||
public function handle(PostCreated $event): void
|
||||
{
|
||||
if (! PostHogService::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workspace = $event->post->workspace;
|
||||
|
||||
SyncAccountUsage::dispatch((string) $workspace->account_id, (string) $workspace->id);
|
||||
}
|
||||
}
|
||||
24
app/Listeners/PostHog/SyncUsageOnPostDeleted.php
Normal file
24
app/Listeners/PostHog/SyncUsageOnPostDeleted.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners\PostHog;
|
||||
|
||||
use App\Events\PostDeleted;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\PostHogService;
|
||||
|
||||
class SyncUsageOnPostDeleted
|
||||
{
|
||||
public function handle(PostDeleted $event): void
|
||||
{
|
||||
if (! PostHogService::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workspace = Workspace::findOrFail($event->workspaceId);
|
||||
|
||||
SyncAccountUsage::dispatch((string) $workspace->account_id, (string) $workspace->id);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@
|
|||
use App\Enums\SocialAccount\Status;
|
||||
use App\Jobs\SendNotification;
|
||||
use App\Mail\AccountDisconnected;
|
||||
use App\Observers\SocialAccountObserver;
|
||||
use Database\Factories\SocialAccountFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
|
@ -21,6 +23,7 @@
|
|||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
#[ObservedBy(SocialAccountObserver::class)]
|
||||
class SocialAccount extends Model
|
||||
{
|
||||
/** @use HasFactory<SocialAccountFactory> */
|
||||
|
|
|
|||
34
app/Observers/SocialAccountObserver.php
Normal file
34
app/Observers/SocialAccountObserver.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Services\PostHogService;
|
||||
|
||||
class SocialAccountObserver
|
||||
{
|
||||
public function created(SocialAccount $socialAccount): void
|
||||
{
|
||||
$this->syncUsage($socialAccount);
|
||||
}
|
||||
|
||||
public function deleted(SocialAccount $socialAccount): void
|
||||
{
|
||||
$this->syncUsage($socialAccount);
|
||||
}
|
||||
|
||||
private function syncUsage(SocialAccount $socialAccount): void
|
||||
{
|
||||
if (! PostHogService::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncAccountUsage::dispatch(
|
||||
(string) $socialAccount->workspace->account_id,
|
||||
(string) $socialAccount->workspace_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
132
tests/Feature/Jobs/PostHog/SyncAccountUsageTest.php
Normal file
132
tests/Feature/Jobs/PostHog/SyncAccountUsageTest.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Jobs\PostHog\SendEvent;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Models\Account;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Post;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\PostHogService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
|
||||
|
||||
$this->account = Account::factory()->create([
|
||||
'plan_id' => Plan::query()->where('slug', 'starter')->first()?->id,
|
||||
]);
|
||||
$this->user = User::factory()->create(['account_id' => $this->account->id]);
|
||||
$this->account->update(['owner_id' => $this->user->id]);
|
||||
});
|
||||
|
||||
test('handle is a no-op when api key is unset', function () {
|
||||
config(['services.posthog.api_key' => null]);
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) $this->account->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
});
|
||||
|
||||
test('handle returns silently when account does not exist', function () {
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) Str::uuid()))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
});
|
||||
|
||||
test('handle group-identifies the account with usage metrics', function () {
|
||||
$workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
SocialAccount::factory()->count(2)->create(['workspace_id' => $workspace->id]);
|
||||
Post::factory()->count(3)->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) $this->account->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertPushed(SendEvent::class, function ($job) {
|
||||
if ($job->method !== 'groupIdentify' || $job->payload['groupType'] !== 'account') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$props = $job->payload['properties'];
|
||||
|
||||
return $job->payload['groupKey'] === (string) $this->account->id
|
||||
&& $props['workspaces_count'] === 1
|
||||
&& $props['social_accounts_count'] === 2
|
||||
&& $props['posts_count'] === 3;
|
||||
});
|
||||
});
|
||||
|
||||
test('handle group-identifies the workspace when workspaceId is provided', function () {
|
||||
$workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
SocialAccount::factory()->create(['workspace_id' => $workspace->id]);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) $this->account->id, (string) $workspace->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertPushed(SendEvent::class, function ($job) use ($workspace) {
|
||||
return $job->method === 'groupIdentify'
|
||||
&& $job->payload['groupType'] === 'workspace'
|
||||
&& $job->payload['groupKey'] === (string) $workspace->id
|
||||
&& $job->payload['properties']['account_id'] === (string) $this->account->id
|
||||
&& $job->payload['properties']['social_accounts_count'] === 1;
|
||||
});
|
||||
});
|
||||
|
||||
test('handle skips workspace group identify when workspaceId is null', function () {
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) $this->account->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertNotPushed(SendEvent::class, function ($job) {
|
||||
return $job->method === 'groupIdentify' && $job->payload['groupType'] === 'workspace';
|
||||
});
|
||||
});
|
||||
|
||||
test('handle invalidates the posts_count cache before reading usage', function () {
|
||||
$workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Cache::put("account:{$this->account->id}:posts_count", 999, 300);
|
||||
|
||||
Post::factory()->count(2)->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
(new SyncAccountUsage((string) $this->account->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertPushed(SendEvent::class, function ($job) {
|
||||
return $job->method === 'groupIdentify'
|
||||
&& $job->payload['groupType'] === 'account'
|
||||
&& $job->payload['properties']['posts_count'] === 2;
|
||||
});
|
||||
});
|
||||
|
||||
test('job is queued on the posthog connection queue', function () {
|
||||
$job = new SyncAccountUsage((string) $this->account->id);
|
||||
|
||||
expect($job->queue)->toBe('posthog');
|
||||
});
|
||||
|
|
@ -3,11 +3,10 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use App\Jobs\PostHog\SendEvent;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Jobs\PostHog\SyncUser;
|
||||
use App\Models\Account;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Post;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\PostHogService;
|
||||
|
|
@ -55,69 +54,31 @@
|
|||
});
|
||||
});
|
||||
|
||||
test('handle group-identifies the account with usage metrics', function () {
|
||||
$workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
SocialAccount::factory()->count(2)->create(['workspace_id' => $workspace->id]);
|
||||
Post::factory()->count(3)->create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
test('handle dispatches SyncAccountUsage with the user account id', function () {
|
||||
Queue::fake();
|
||||
|
||||
(new SyncUser((string) $this->user->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertPushed(SendEvent::class, function ($job) {
|
||||
if ($job->method !== 'groupIdentify' || $job->payload['groupType'] !== 'account') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$props = $job->payload['properties'];
|
||||
|
||||
return $job->payload['groupKey'] === (string) $this->account->id
|
||||
&& $props['workspaces_count'] === 1
|
||||
&& $props['social_accounts_count'] === 2
|
||||
&& $props['posts_count'] === 3
|
||||
&& $props['members_count'] === 1
|
||||
&& array_key_exists('plan', $props)
|
||||
&& array_key_exists('has_active_subscription', $props)
|
||||
&& array_key_exists('is_on_trial', $props);
|
||||
Queue::assertPushed(SyncAccountUsage::class, function ($job) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === null;
|
||||
});
|
||||
});
|
||||
|
||||
test('handle group-identifies the current workspace when set', function () {
|
||||
test('handle dispatches SyncAccountUsage with the current workspace when set', function () {
|
||||
$workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
SocialAccount::factory()->create(['workspace_id' => $workspace->id]);
|
||||
$this->user->update(['current_workspace_id' => $workspace->id]);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
(new SyncUser((string) $this->user->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertPushed(SendEvent::class, function ($job) use ($workspace) {
|
||||
return $job->method === 'groupIdentify'
|
||||
&& $job->payload['groupType'] === 'workspace'
|
||||
&& $job->payload['groupKey'] === (string) $workspace->id
|
||||
&& $job->payload['properties']['account_id'] === (string) $this->account->id
|
||||
&& $job->payload['properties']['social_accounts_count'] === 1;
|
||||
});
|
||||
});
|
||||
|
||||
test('handle skips workspace group identify when user has no current workspace', function () {
|
||||
$this->user->update(['current_workspace_id' => null]);
|
||||
|
||||
Queue::fake();
|
||||
|
||||
(new SyncUser((string) $this->user->id))->handle(app(PostHogService::class));
|
||||
|
||||
Queue::assertNotPushed(SendEvent::class, function ($job) {
|
||||
return $job->method === 'groupIdentify' && $job->payload['groupType'] === 'workspace';
|
||||
Queue::assertPushed(SyncAccountUsage::class, function ($job) use ($workspace) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === (string) $workspace->id;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Events\PostCreated;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Listeners\PostHog\SyncUsageOnPostCreated;
|
||||
use App\Models\Account;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
|
||||
|
||||
$this->account = Account::factory()->create();
|
||||
$this->user = User::factory()->create(['account_id' => $this->account->id]);
|
||||
$this->account->update(['owner_id' => $this->user->id]);
|
||||
$this->workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('listener dispatches SyncAccountUsage with the account and workspace ids', function () {
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
(new SyncUsageOnPostCreated)->handle(new PostCreated($post));
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class, function ($job) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === (string) $this->workspace->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('listener is wired to the PostCreated event via auto-discovery', function () {
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
PostCreated::dispatch($post);
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
|
||||
test('listener does not dispatch when PostHog is disabled', function () {
|
||||
config(['services.posthog.enabled' => false]);
|
||||
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
(new SyncUsageOnPostCreated)->handle(new PostCreated($post));
|
||||
|
||||
Bus::assertNotDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Events\PostDeleted;
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Listeners\PostHog\SyncUsageOnPostDeleted;
|
||||
use App\Models\Account;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
|
||||
|
||||
$this->account = Account::factory()->create();
|
||||
$this->user = User::factory()->create(['account_id' => $this->account->id]);
|
||||
$this->account->update(['owner_id' => $this->user->id]);
|
||||
$this->workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('listener dispatches SyncAccountUsage with the account and workspace ids', function () {
|
||||
Bus::fake();
|
||||
|
||||
(new SyncUsageOnPostDeleted)->handle(new PostDeleted(
|
||||
postId: (string) Str::uuid(),
|
||||
workspaceId: (string) $this->workspace->id,
|
||||
));
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class, function ($job) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === (string) $this->workspace->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('listener is wired to the PostDeleted event via auto-discovery', function () {
|
||||
Bus::fake();
|
||||
|
||||
PostDeleted::dispatch((string) Str::uuid(), (string) $this->workspace->id);
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
|
||||
test('listener does not dispatch when PostHog is disabled', function () {
|
||||
config(['services.posthog.enabled' => false]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
(new SyncUsageOnPostDeleted)->handle(new PostDeleted(
|
||||
postId: (string) Str::uuid(),
|
||||
workspaceId: (string) $this->workspace->id,
|
||||
));
|
||||
|
||||
Bus::assertNotDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
66
tests/Feature/Observers/SocialAccountObserverTest.php
Normal file
66
tests/Feature/Observers/SocialAccountObserverTest.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Jobs\PostHog\SyncAccountUsage;
|
||||
use App\Models\Account;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
|
||||
|
||||
$this->account = Account::factory()->create();
|
||||
$this->user = User::factory()->create(['account_id' => $this->account->id]);
|
||||
$this->account->update(['owner_id' => $this->user->id]);
|
||||
$this->workspace = Workspace::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('creating a social account dispatches SyncAccountUsage', function () {
|
||||
Bus::fake();
|
||||
|
||||
SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]);
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class, function ($job) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === (string) $this->workspace->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('deleting a social account dispatches SyncAccountUsage', function () {
|
||||
$socialAccount = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
$socialAccount->delete();
|
||||
|
||||
Bus::assertDispatched(SyncAccountUsage::class, function ($job) {
|
||||
return $job->accountId === (string) $this->account->id
|
||||
&& $job->workspaceId === (string) $this->workspace->id;
|
||||
});
|
||||
});
|
||||
|
||||
test('updating a social account does not dispatch SyncAccountUsage', function () {
|
||||
$socialAccount = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
$socialAccount->update(['is_active' => false]);
|
||||
|
||||
Bus::assertNotDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
|
||||
test('does not dispatch when PostHog is disabled', function () {
|
||||
config(['services.posthog.enabled' => false]);
|
||||
|
||||
Bus::fake();
|
||||
|
||||
SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]);
|
||||
|
||||
Bus::assertNotDispatched(SyncAccountUsage::class);
|
||||
});
|
||||
Loading…
Reference in a new issue