tenant = Tenant::factory()->create(); $this->otherTenant = Tenant::factory()->create(); $this->user = User::factory()->for($this->tenant)->create(); $this->actingAs($this->user); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $this->project = Project::factory()->for($this->tenant)->create(); $this->manager = User::factory()->for($this->tenant)->create(['is_active' => true]); }); test('PATCH /api/deals/{id} 401 без auth', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create(); auth()->logout(); $this->patchJson('/api/deals/'.$deal->id, [])->assertStatus(401); }); test('PATCH /api/deals/{id} 404 чужая сделка', function () { DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id); $foreignProject = Project::factory()->for($this->otherTenant)->create(); $foreign = Deal::factory()->for($this->otherTenant)->for($foreignProject)->create(); $this->patchJson('/api/deals/'.$foreign->id, [ 'comment' => 'leak', ])->assertStatus(404); }); test('PATCH /api/deals/{id} обновляет comment + пишет deal.commented в ActivityLog', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create(['comment' => 'old']); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'comment' => 'Дозвонился, перезвоню после 14:00', ]); $r->assertStatus(200); expect($r->json('deal.comment'))->toBe('Дозвонился, перезвоню после 14:00'); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $deal->refresh(); expect($deal->comment)->toBe('Дозвонился, перезвоню после 14:00'); $log = ActivityLog::where('deal_id', $deal->id)->where('event', 'deal.commented')->first(); expect($log)->not->toBeNull(); expect($log->context['text'])->toBe('Дозвонился, перезвоню после 14:00'); }); test('PATCH /api/deals/{id} обновляет manager_id + пишет deal.assigned + ставит assigned_at', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create([ 'manager_id' => null, 'assigned_at' => null, ]); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'manager_id' => $this->manager->id, ]); $r->assertStatus(200); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $deal->refresh(); expect($deal->manager_id)->toBe($this->manager->id); expect($deal->assigned_at)->not->toBeNull(); $log = ActivityLog::where('deal_id', $deal->id)->where('event', 'deal.assigned')->first(); expect($log)->not->toBeNull(); expect($log->context['to'])->toBe($this->manager->id); }); test('PATCH /api/deals/{id} обновляет status + пишет deal.status_changed source=manual', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'status' => 'won', ]); $r->assertStatus(200); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $deal->refresh(); expect($deal->status)->toBe('won'); $log = ActivityLog::where('deal_id', $deal->id)->where('event', 'deal.status_changed')->first(); expect($log)->not->toBeNull(); expect($log->context)->toMatchArray(['from' => 'new', 'to' => 'won', 'source' => 'manual']); }); test('PATCH /api/deals/{id} 422 на неизвестный status slug', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create(); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'status' => 'not_a_real_slug', ]); $r->assertStatus(422); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $deal->refresh(); expect($deal->status)->toBe('new'); // не изменился }); test('PATCH /api/deals/{id} 422 на manager_id чужого tenant\'а', function () { DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id); $foreignManager = User::factory()->for($this->otherTenant)->create(['is_active' => true]); $deal = Deal::factory()->for($this->tenant)->for($this->project)->create(); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'manager_id' => $foreignManager->id, ]); $r->assertStatus(422); }); test('PATCH /api/deals/{id} NO-OP не пишет ActivityLog', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create([ 'status' => 'won', 'comment' => 'same', ]); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'status' => 'won', // не меняем 'comment' => 'same', // не меняем ]); $r->assertStatus(200); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); expect(ActivityLog::where('deal_id', $deal->id)->count())->toBe(0); }); test('PATCH /api/deals/{id} комбинированно — comment + status одним запросом → 2 ActivityLog', function () { $deal = Deal::factory()->for($this->tenant)->for($this->project)->create([ 'status' => 'new', 'comment' => null, ]); $r = $this->patchJson('/api/deals/'.$deal->id, [ 'comment' => 'Заметка', 'status' => 'in_progress', ]); $r->assertStatus(200); DB::statement('SET app.current_tenant_id = '.$this->tenant->id); $events = ActivityLog::where('deal_id', $deal->id)->orderBy('id')->get(); expect($events)->toHaveCount(2); $eventNames = $events->pluck('event')->all(); expect($eventNames)->toContain('deal.commented'); expect($eventNames)->toContain('deal.status_changed'); });