This commit completes the PR #53 integration by optimizing the test suite: Performance Improvements: - Migrated 25 test files from RefreshDatabase to DatabaseTransactions - Tests now run in 12.69s parallel (previously 30s+) - Increased PostgreSQL max_locks_per_transaction to 256 for parallel testing Test Infrastructure Changes: - Disabled broadcasting in tests (set to null) to avoid Reverb connectivity issues - Reverted 5 integration tests to RefreshDatabase (CheckoutFlowTest + 4 Service tests) that require full schema recreation due to complex fixtures PR #53 Integration Fixes: - Added Product.inStock() scope for inventory queries - Fixed ProductFactory to create InventoryItem records instead of using removed columns - Added Department.products() relationship - Fixed FulfillmentWorkOrderController view variables - Fixed orders migration location_id foreign key constraint - Created seller-layout component wrapper All 146 tests now pass with optimal performance.
221 lines
6.8 KiB
PHP
221 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Analytics;
|
|
|
|
use App\Models\Analytics\AnalyticsEvent;
|
|
use App\Models\Analytics\BuyerEngagementScore;
|
|
use App\Models\Analytics\ProductView;
|
|
use App\Models\Business;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Tests\TestCase;
|
|
|
|
class AnalyticsSecurityTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
protected User $sellerUser1;
|
|
|
|
protected User $sellerUser2;
|
|
|
|
protected Business $business1;
|
|
|
|
protected Business $business2;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Create two separate businesses and users
|
|
$this->business1 = Business::factory()->create(['name' => 'Business 1']);
|
|
$this->business2 = Business::factory()->create(['name' => 'Business 2']);
|
|
|
|
$this->sellerUser1 = User::factory()->create(['user_type' => 'seller']);
|
|
$this->sellerUser2 = User::factory()->create(['user_type' => 'seller']);
|
|
|
|
// Attach users to their businesses
|
|
$this->sellerUser1->businesses()->attach($this->business1->id, [
|
|
'is_primary' => true,
|
|
'permissions' => ['analytics.overview', 'analytics.products'],
|
|
]);
|
|
|
|
$this->sellerUser2->businesses()->attach($this->business2->id, [
|
|
'is_primary' => true,
|
|
'permissions' => ['analytics.overview'],
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function analytics_events_are_scoped_to_business()
|
|
{
|
|
// Create events for both businesses
|
|
$event1 = AnalyticsEvent::create([
|
|
'business_id' => $this->business1->id,
|
|
'event_type' => 'product_view',
|
|
'event_category' => 'product',
|
|
'event_action' => 'view',
|
|
]);
|
|
|
|
$event2 = AnalyticsEvent::create([
|
|
'business_id' => $this->business2->id,
|
|
'event_type' => 'product_view',
|
|
'event_category' => 'product',
|
|
'event_action' => 'view',
|
|
]);
|
|
|
|
// Login as business 1 user
|
|
$this->actingAs($this->sellerUser1);
|
|
|
|
// Should only see business 1 events
|
|
$events = AnalyticsEvent::all();
|
|
$this->assertCount(1, $events);
|
|
$this->assertEquals($this->business1->id, $events->first()->business_id);
|
|
|
|
// Login as business 2 user
|
|
$this->actingAs($this->sellerUser2);
|
|
|
|
// Should only see business 2 events
|
|
$events = AnalyticsEvent::all();
|
|
$this->assertCount(1, $events);
|
|
$this->assertEquals($this->business2->id, $events->first()->business_id);
|
|
}
|
|
|
|
/** @test */
|
|
public function product_views_are_scoped_to_business()
|
|
{
|
|
// Create product views for both businesses
|
|
ProductView::create([
|
|
'business_id' => $this->business1->id,
|
|
'product_id' => 1,
|
|
'viewed_at' => now(),
|
|
]);
|
|
|
|
ProductView::create([
|
|
'business_id' => $this->business2->id,
|
|
'product_id' => 2,
|
|
'viewed_at' => now(),
|
|
]);
|
|
|
|
// Login as business 1 user
|
|
$this->actingAs($this->sellerUser1);
|
|
$this->assertCount(1, ProductView::all());
|
|
|
|
// Login as business 2 user
|
|
$this->actingAs($this->sellerUser2);
|
|
$this->assertCount(1, ProductView::all());
|
|
}
|
|
|
|
/** @test */
|
|
public function buyer_engagement_scores_are_scoped_to_business()
|
|
{
|
|
// Create scores for both businesses
|
|
BuyerEngagementScore::create([
|
|
'business_id' => $this->business1->id,
|
|
'buyer_business_id' => 100,
|
|
'total_score' => 85,
|
|
]);
|
|
|
|
BuyerEngagementScore::create([
|
|
'business_id' => $this->business2->id,
|
|
'buyer_business_id' => 200,
|
|
'total_score' => 75,
|
|
]);
|
|
|
|
// Login as business 1 user
|
|
$this->actingAs($this->sellerUser1);
|
|
$scores = BuyerEngagementScore::all();
|
|
$this->assertCount(1, $scores);
|
|
$this->assertEquals(100, $scores->first()->buyer_business_id);
|
|
|
|
// Login as business 2 user
|
|
$this->actingAs($this->sellerUser2);
|
|
$scores = BuyerEngagementScore::all();
|
|
$this->assertCount(1, $scores);
|
|
$this->assertEquals(200, $scores->first()->buyer_business_id);
|
|
}
|
|
|
|
/** @test */
|
|
public function users_cannot_access_analytics_without_permission()
|
|
{
|
|
// User 2 doesn't have analytics.products permission
|
|
$this->actingAs($this->sellerUser2);
|
|
|
|
$response = $this->get(route('seller.business.analytics.products', $this->business2->slug));
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
/** @test */
|
|
public function users_can_access_analytics_with_permission()
|
|
{
|
|
// User 1 has analytics.products permission
|
|
$this->actingAs($this->sellerUser1);
|
|
|
|
$response = $this->get(route('seller.business.analytics.products', $this->business1->slug));
|
|
|
|
$response->assertStatus(200);
|
|
}
|
|
|
|
/** @test */
|
|
public function users_cannot_access_other_business_analytics()
|
|
{
|
|
$this->actingAs($this->sellerUser1);
|
|
|
|
// Try to access business 2's analytics
|
|
$response = $this->get(route('seller.business.analytics.index', $this->business2->slug));
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
/** @test */
|
|
public function for_business_scope_removes_global_scope()
|
|
{
|
|
// Create events for both businesses
|
|
AnalyticsEvent::create([
|
|
'business_id' => $this->business1->id,
|
|
'event_type' => 'test1',
|
|
'event_category' => 'test',
|
|
'event_action' => 'test',
|
|
]);
|
|
|
|
AnalyticsEvent::create([
|
|
'business_id' => $this->business2->id,
|
|
'event_type' => 'test2',
|
|
'event_category' => 'test',
|
|
'event_action' => 'test',
|
|
]);
|
|
|
|
// Login as business 1 user
|
|
$this->actingAs($this->sellerUser1);
|
|
|
|
// Using forBusiness scope should allow access to specific business data
|
|
$events = AnalyticsEvent::forBusiness($this->business1->id)->get();
|
|
$this->assertCount(1, $events);
|
|
$this->assertEquals($this->business1->id, $events->first()->business_id);
|
|
}
|
|
|
|
/** @test */
|
|
public function unauthenticated_users_cannot_access_analytics()
|
|
{
|
|
$response = $this->get(route('seller.business.analytics.index', $this->business1->slug));
|
|
|
|
$response->assertRedirect(route('login'));
|
|
}
|
|
|
|
/** @test */
|
|
public function analytics_models_auto_set_business_id_on_creation()
|
|
{
|
|
$this->actingAs($this->sellerUser1);
|
|
|
|
// Create event without specifying business_id
|
|
$event = AnalyticsEvent::create([
|
|
'event_type' => 'test',
|
|
'event_category' => 'test',
|
|
'event_action' => 'test',
|
|
]);
|
|
|
|
// Should auto-set to current user's business
|
|
$this->assertEquals($this->business1->id, $event->business_id);
|
|
}
|
|
}
|