create(); VerifyWorkspaceConnections::dispatch($workspace); Mail::assertNothingSent(); }); test('job does not send email when all connections are valid', function () { Mail::fake(); $workspace = Workspace::factory()->create(); SocialAccount::factory()->linkedin()->create(['workspace_id' => $workspace->id]); SocialAccount::factory()->x()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldReceive('verify')->andReturn(true); app()->instance(ConnectionVerifier::class, $verifier); VerifyWorkspaceConnections::dispatch($workspace); Mail::assertNothingSent(); }); test('job marks account as token expired on first failure and disconnected on second', function () { Mail::fake(); $workspace = Workspace::factory()->create(); $account = SocialAccount::factory()->linkedin()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldReceive('verify') ->andThrow(new TokenExpiredException('Token expired')); app()->instance(ConnectionVerifier::class, $verifier); // First run — marks as TokenExpired VerifyWorkspaceConnections::dispatch($workspace); expect($account->fresh()->status)->toBe(Status::TokenExpired); expect($account->fresh()->error_message)->toBe('Token expired'); // Second run — escalates to Disconnected VerifyWorkspaceConnections::dispatch($workspace); expect($account->fresh()->status)->toBe(Status::Disconnected); Mail::assertQueued(WorkspaceConnectionsDisconnected::class, function ($mail) use ($workspace) { return $mail->workspace->id === $workspace->id && $mail->disconnectedAccounts->count() === 1; }); }); test('job sends single email with all failed accounts', function () { Mail::fake(); $workspace = Workspace::factory()->create(); $account1 = SocialAccount::factory()->linkedin()->create(['workspace_id' => $workspace->id]); $account2 = SocialAccount::factory()->x()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldReceive('verify') ->andThrow(new TokenExpiredException('Token expired')); app()->instance(ConnectionVerifier::class, $verifier); VerifyWorkspaceConnections::dispatch($workspace); expect($account1->fresh()->status)->toBe(Status::TokenExpired); expect($account2->fresh()->status)->toBe(Status::TokenExpired); Mail::assertQueued(WorkspaceConnectionsDisconnected::class, function ($mail) use ($workspace) { return $mail->workspace->id === $workspace->id && $mail->disconnectedAccounts->count() === 2; }); Mail::assertQueuedCount(1); }); test('job only includes failed accounts in email', function () { Mail::fake(); $workspace = Workspace::factory()->create(); $validAccount = SocialAccount::factory()->linkedin()->create(['workspace_id' => $workspace->id]); $invalidAccount = SocialAccount::factory()->x()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldReceive('verify') ->with(Mockery::on(fn ($acc) => $acc->id === $validAccount->id)) ->andReturn(true); $verifier->shouldReceive('verify') ->with(Mockery::on(fn ($acc) => $acc->id === $invalidAccount->id)) ->andThrow(new TokenExpiredException('Token expired')); app()->instance(ConnectionVerifier::class, $verifier); VerifyWorkspaceConnections::dispatch($workspace); expect($validAccount->fresh()->status)->toBe(Status::Connected); expect($invalidAccount->fresh()->status)->toBe(Status::TokenExpired); Mail::assertQueued(WorkspaceConnectionsDisconnected::class, function ($mail) use ($invalidAccount) { return $mail->disconnectedAccounts->count() === 1 && $mail->disconnectedAccounts->first()->id === $invalidAccount->id; }); }); test('job does NOT disconnect or email when platform is unavailable', function () { Mail::fake(); $workspace = Workspace::factory()->create(); $account = SocialAccount::factory()->bluesky()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldReceive('verify')->andThrow( new PlatformUnavailableException('Bluesky API returned 503 during token refresh', 503) ); app()->instance(ConnectionVerifier::class, $verifier); VerifyWorkspaceConnections::dispatch($workspace); expect($account->fresh()->status)->toBe(Status::Connected); Mail::assertNothingQueued(); }); test('job skips already disconnected accounts', function () { Mail::fake(); $workspace = Workspace::factory()->create(); SocialAccount::factory()->linkedin()->disconnected()->create(['workspace_id' => $workspace->id]); $verifier = mock(ConnectionVerifier::class); $verifier->shouldNotReceive('verify'); app()->instance(ConnectionVerifier::class, $verifier); VerifyWorkspaceConnections::dispatch($workspace); Mail::assertNothingSent(); });